import datetime
import functools
import logging
import signal
import math

from datetime import date, timedelta, datetime
from enum import Enum

from absl import app, flags
from tornado.ioloop import PeriodicCallback

from collections import deque
from dataclasses import dataclass

from coin.base.datetime_util import (to_datetime, to_timestamp_int)
from coin.base.timestamp import get_timestamp
from coin.base.param_util import to_list
from coin.exchange.base.order_gateway_config import convert_og_config_from_dict
from coin.exchange.base.strategy_logger import StrategyLogProvider
from coin.exchange.kr_rest.product.product_impl import (generate_product_from_subreq)
from coin.exchange.kr_rest.product_holders import get_holder_from_product
from coin.exchange.util.feed_checker import get_default_feed_checker_config
from coin.proto.coin_feed_pb2 import BestBookEntry, BookEntry
from coin.proto.coin_request_pb2 import AccountRequestProto
from coin.proto.coin_telemetry_pb2 import LatencyProto

from coin.strategy.accounting.pnl_balance_printer.util import (get_quote_currency, reserve_as_proto)
from coin.strategy.executor.aggressive.util import (convert_executor_config_from_dict)
from coin.strategy.executor.aggressive.factory import construct_agg_executor
from coin.strategy.executor.util.cancel_all_executor import (SimpleCancelAllExecutor)
from coin.strategy.hk_hard_arb.index_pricer import IndexPricer
from coin.strategy.hk_hard_arb.pricer import PositionLeanPricer
from coin.strategy.hk_hard_arb.util import load_clean_json
from coin.strategy.mm.base.bookmap import BookMap
from coin.strategy.mm.base.unary_multifeed_strategy_base import UnaryMultifeedStrategyBase

from coin.strategy.mm.subscription import (
    FeedSubscriptionRequest,
    SubscriptionRequest,
)
from coin.strategy.tool.fill_log_dumper import FillLogDumper
from coin.strategy.hk_hard_arb.amplitude_checker import AmplitudeChecker
from coin.strategy.accounting.pnl_balance_printer.factory import get_pnl_balance_printer_cls
from coin.strategy.util.request_util import convert_strategy_request_from_dict
from coin.support.telemetry.latency_recorder import LatencyRecorder, LifeOfSignal
from coin.strategy.hk_hard_arb.unhedged_mm_3 import (
    PassUnhedgeStrategyConfig,
)
from coin.exchange.base.order_gateway import OrderSide
from coin.experimental.leon.simple_sim.run_from_archive import (
    run_from_archive_or_cache, define_fastfeed_flags)

from coin.strategy.dex_strat.simple_amm.amm_utils import (
  WindowEstimator, BiasCalculator, 
  BiasEntry, BiasValidator, BiasEntry2, BiasValidator2, BiasValidator3, 
  SimpleBookEntry
  )

FLAGS = flags.FLAGS

class SingleProductSubStrategyAMMGeneralBase(object):
  def __init__(self, model_profile, logger, parent):
    self._logger = logger
    self._parent = parent
    config = model_profile.config_dict
    self._config = config
    self._trade_subreq = model_profile.trade_subreq
    self._trade_product = model_profile.trade_product
    self._ref_subreq = model_profile.ref_subreq
    self._ref_product = model_profile.ref_product
    assert len(self._ref_subreq) == len(self._ref_product)

    self._edge = None
    self._spread = None
    self._spread_hard = None
    self._pull_edge = None

    self._bbo_protection = config.get('bbo_protection', False)
    self._edge = config['edge_bp'] / 10000.

    products = self._ref_product + to_list(self._trade_product)
    self._bookmap = BookMap(products, logger=self._logger)

    tick = get_holder_from_product(self._trade_product).product_info.first_tick_size
    self._pricer = IndexPricer(self._config, tick, use_fastfeed=True)
    self._pos_lean_pricer = PositionLeanPricer(min_pos=config['min_pos'],
                                               max_pos=config['max_pos'],
                                               lot_size=config['lot_size'],
                                               lean_edge_bp=config.get('lean_edge_bp', 0))

    self._exit = False

    self._last_book_write_ts = get_timestamp()
    self._last_binance_book_ts = 0
    self._last_uniswap_book_ts = 0
    self._last_binance_ask0 = 0.0
    self._last_binance_bid0 = 0.0
    self._last_uniswap_ask0 = 0.0
    self._last_uniswap_bid0 = 0.0
    self._kmax_diff_ms = int(self._config.get('max_diff_ms', 500))

    self._last_book_dict = {}
    for product in products:
      self._last_book_dict[product.exchange] = {}
      self._last_book_dict[product.exchange][product.symbol] = None

  def set_exit(self):
    self._exit = True

  def prepare_base(self, feed_subsystem):
    assert feed_subsystem is not None
    product = self._trade_product
    subreq = self._trade_subreq
    trade_feed_sub_request = FeedSubscriptionRequest.create(to_list(product), subreq)
    feed_subsystem.subscribe_book(trade_feed_sub_request, functools.partial(
        self.on_book, self._trade_product, self._trade_subreq))
    for product, sub_req in zip(self._ref_product, self._ref_subreq):
      feed_sub_request = FeedSubscriptionRequest.create(to_list(product), sub_req)
      feed_subsystem.subscribe_book(
          feed_sub_request, functools.partial(self.on_book, product, sub_req))

    feed_subsystem.subscribe_trade(trade_feed_sub_request, functools.partial(
        self.on_trade, self._trade_product, self._trade_subreq))
    for product, sub_req in zip(self._ref_product, self._ref_subreq):
      feed_sub_request = FeedSubscriptionRequest.create(to_list(product), sub_req)
      feed_subsystem.subscribe_trade(
          feed_sub_request, functools.partial(self.on_trade, product, sub_req))
    return True

  def on_book_base(self, product, book):
    if self._exit:
      return False

    self._bookmap.set_book(product, book)

    # Check book freshness and sanity
    if not self._bookmap.is_book_ready() or not self._bookmap.is_book_sane():
      return False

    ref_book = [self._bookmap.get_book(product) for product in self._ref_product]
    trade_book = self._bookmap.get_book(self._trade_product)
    # self._pricer.update(ref_book, trade_book)
    
    if self._last_book_dict[product.exchange].get(product.symbol) is None \
    or book.timestamp - self._last_book_dict[product.exchange][product.symbol].timestamp >= 500 * 10 ** 6:
      self._last_book_dict[product.exchange][product.symbol] = SimpleBookEntry(book.timestamp, product.exchange, product.symbol, book.ask0().price, book.bid0().price)
      self._logger.info('%s Book product:%s,%s, askp: %s, bidp: %s' % (
            to_datetime(book.timestamp), product.exchange, product.symbol, book.ask0().price, book.bid0().price))
    else:
      return False
      
    #if not self._pricer.ready:
    #  return False

    return True

  def on_trade_base(self, product, trade):
    if product.exchange == 'Uniswap':
      self._logger.info('%s Trade product: %s, price: %s, qty: %s, side: %s' % (
        to_datetime(trade.timestamp), product.symbol, trade.price, trade.qty, trade.side))
    return True

  def on_og_reset(self, *, order_gateway):
    self._logger.info('Intializing executor...')
    executor_params = convert_executor_config_from_dict(self._trade_product.symbol, self._config)
    self._executor = construct_agg_executor(self._trade_subreq,
                                            executor_params,
                                            order_gateway,
                                            self._logger,
                                            latency_recorder=self._latency_recorder)
    self._strategy_logger.write_executor_config(executor_params)

  def prepare_process(self, feed_subsystem):
    return

  def on_book_process(self, product, book):
    return

  def on_trade_process(elf, product, trade):
    return

  def prepare(self, feed_subsystem):
    if not self.prepare_base(feed_subsystem):
      return
    self.prepare_process(feed_subsystem)

  def on_book(self, product, book):
    if not self.on_book_base(product, book):
      return
    self.on_book_process(product, book)

  def on_trade(self, product, trade):
    if not self.on_trade_base(product, trade):
      return
    self.on_trade_process(product, trade)

class SingleProductSubStrategyAMMBase(object):
  def __init__(self, model_profile, logger, parent):
    self._logger = logger
    self._parent = parent
    config = model_profile.config_dict
    self._config = config
    self._trade_subreq = model_profile.trade_subreq
    self._trade_product = model_profile.trade_product
    self._ref_subreq = model_profile.ref_subreq
    self._ref_product = model_profile.ref_product
    assert len(self._ref_subreq) == len(self._ref_product)

    self._edge = None
    self._spread = None
    self._spread_hard = None
    self._pull_edge = None

    self._bbo_protection = config.get('bbo_protection', False)
    self._edge = config['edge_bp'] / 10000.

    products = self._ref_product + to_list(self._trade_product)
    self._bookmap = BookMap(products, logger=self._logger)

    tick = get_holder_from_product(self._trade_product).product_info.first_tick_size
    self._pricer = IndexPricer(self._config, tick, use_fastfeed=True)
    self._pos_lean_pricer = PositionLeanPricer(min_pos=config['min_pos'],
                                               max_pos=config['max_pos'],
                                               lot_size=config['lot_size'],
                                               lean_edge_bp=config.get('lean_edge_bp', 0))

    self._exit = False

    self._last_book_write_ts = get_timestamp()
    self._last_binance_book_ts = 0
    self._last_uniswap_book_ts = 0
    self._last_binance_ask0 = 0.0
    self._last_binance_bid0 = 0.0
    self._last_uniswap_ask0 = 0.0
    self._last_uniswap_bid0 = 0.0
    self._kmax_diff_ms = int(self._config.get('max_diff_ms', 500))

  def set_exit(self):
    self._exit = True

  def prepare_base(self, feed_subsystem):
    assert feed_subsystem is not None
    product = self._trade_product
    subreq = self._trade_subreq
    trade_feed_sub_request = FeedSubscriptionRequest.create(to_list(product), subreq)
    feed_subsystem.subscribe_book(trade_feed_sub_request, functools.partial(
        self.on_book, self._trade_product, self._trade_subreq))
    for product, sub_req in zip(self._ref_product, self._ref_subreq):
      feed_sub_request = FeedSubscriptionRequest.create(to_list(product), sub_req)
      feed_subsystem.subscribe_book(
          feed_sub_request, functools.partial(self.on_book, product, sub_req))

    feed_subsystem.subscribe_trade(trade_feed_sub_request, functools.partial(
        self.on_trade, self._trade_product, self._trade_subreq))
    for product, sub_req in zip(self._ref_product, self._ref_subreq):
      feed_sub_request = FeedSubscriptionRequest.create(to_list(product), sub_req)
      feed_subsystem.subscribe_trade(
          feed_sub_request, functools.partial(self.on_trade, product, sub_req))
    return True

  def on_book_base(self, product, book):
    if self._exit:
      return False

    self._bookmap.set_book(product, book)

    # Check book freshness and sanity
    if not self._bookmap.is_book_ready() or not self._bookmap.is_book_sane():
      return False

    ref_book = [self._bookmap.get_book(product) for product in self._ref_product]
    trade_book = self._bookmap.get_book(self._trade_product)
    # self._pricer.update(ref_book, trade_book)

    if product.exchange == 'Uniswap':
      if book.timestamp - self._last_uniswap_book_ts >= 200 * 10 ** 6:
        self._last_uniswap_book_ts = book.timestamp
        self._last_uniswap_ask0 = book.ask0().price
        self._last_uniswap_bid0 = book.bid0().price
        self._logger.info('%s Book product: %s, askp: %s, bidp: %s' % (
            to_datetime(book.timestamp), product.symbol, book.ask0().price, book.bid0().price))
      else:
        return False
    elif product.exchange == 'Binance':
      if book.timestamp - self._last_binance_book_ts >= 500 * 10 ** 6:
        self._last_binance_book_ts = book.timestamp
        self._last_binance_ask0 = book.ask0().price
        self._last_binance_bid0 = book.bid0().price
        self._logger.info('%s Book product: %s, askp: %s, bidp: %s' % (
            to_datetime(book.timestamp), product.symbol, book.ask0().price, book.bid0().price))
      else:
        return False
      
    #if not self._pricer.ready:
    #  return False

    return True

  def on_trade_base(self, product, trade):
    if product.exchange == 'Uniswap':
      self._logger.info('%s Trade product: %s, price: %s, qty: %s, side: %s' % (
        to_datetime(trade.timestamp), product.symbol, trade.price, trade.qty, trade.side))
    return True

  def on_og_reset(self, *, order_gateway):
    self._logger.info('Intializing executor...')
    executor_params = convert_executor_config_from_dict(self._trade_product.symbol, self._config)
    self._executor = construct_agg_executor(self._trade_subreq,
                                            executor_params,
                                            order_gateway,
                                            self._logger,
                                            latency_recorder=self._latency_recorder)
    self._strategy_logger.write_executor_config(executor_params)

  def prepare_process(self, feed_subsystem):
    return

  def on_book_process(self, product, book):
    return

  def on_trade_process(elf, product, trade):
    return

  def prepare(self, feed_subsystem):
    if not self.prepare_base(feed_subsystem):
      return
    self.prepare_process(feed_subsystem)

  def on_book(self, product, book):
    if not self.on_book_base(product, book):
      return
    self.on_book_process(product, book)

  def on_trade(self, product, trade):
    if not self.on_trade_base(product, trade):
      return
    self.on_trade_process(product, trade)

class SimpleArbStatisticPriceWindow(SingleProductSubStrategyAMMBase):
  def __init__(self, model_profile, logger, parent):
    super().__init__(model_profile, logger, parent)
    self.last_processed_uniswap_ts = 0
    self._kgas_fee = 0.5
    self._kprice_diff_bar = 1.0
    self._ksignal_cnt_bar = 15
    self.plus_signal_cnt = 0
    self.minus_signal_cnt = 0
    self.total_diff = 0.0

    self.start_uniswap_ts = 0
    self.start_uniswap_ask0 = 0.0
    self.start_uniswap_bid0 = 0.0
    self.start_binance_ask0 = 0.0
    self.start_binance_bid0 = 0.0
    self.last_uniswap_ts = 0
    self.last_uniswap_ask0 = 0.0
    self.last_uniswap_bid0 = 0.0
    self.last_binance_ask0 = 0.0
    self.last_binance_bid0 = 0.0
  
  def process_current_signal(self):
    if self.plus_signal_cnt >= self._ksignal_cnt_bar:
      self._logger.info('Price diff[plus pos]: start from:%s, uniswap_ask0: %s, binance_bid0: %s, diff:%s' % (
            to_datetime(self.start_uniswap_ts), self.start_uniswap_ask0, self.start_binance_bid0, self.start_binance_bid0 - self.start_uniswap_ask0))
      self._logger.info('Price diff[plus pos]: end at:%s, uniswap_ask0: %s, binance_bid0: %s, diff:%s' % (
            to_datetime(self.last_uniswap_ts), self.last_uniswap_ask0, self.last_binance_bid0, self.last_binance_bid0 - self.last_uniswap_ask0))
      self._logger.info('Price diff[plus pos]: avg diff:%s' % (self.total_diff / float(self.plus_signal_cnt)))
    elif self.minus_signal_cnt >= self._ksignal_cnt_bar:
      self._logger.info('Price diff[minus pos]: start from:%s, uniswap_bid0: %s, binance_ask0: %s, diff:%s' % (
            to_datetime(self.start_uniswap_ts), self.start_uniswap_bid0, self.start_binance_ask0, self.start_uniswap_bid0 - self.start_binance_ask0))
      self._logger.info('Price diff[minus pos]: end at:%s, uniswap_bid0: %s, binance_ask0: %s, diff:%s' % (
            to_datetime(self.last_uniswap_ts), self.last_uniswap_bid0, self.last_binance_ask0, self.last_uniswap_bid0 - self.last_binance_ask0))
      self._logger.info('Price diff[minus pos]: avg diff:%s' % (self.total_diff / float(self.minus_signal_cnt)))
      

  def set_last_info(self, uniswap_ts, uniswap_ask0, uniswap_bid0, binance_ask0, binance_bid0):
    self.last_uniswap_ts = uniswap_ts
    self.last_uniswap_ask0 = uniswap_ask0
    self.last_uniswap_bid0 = uniswap_bid0
    self.last_binance_ask0 = binance_ask0
    self.last_binance_bid0 = binance_bid0

  def set_start_info(self, uniswap_ts, uniswap_ask0, uniswap_bid0, binance_ask0, binance_bid0):
    self.start_uniswap_ts = uniswap_ts
    self.start_uniswap_ask0 = uniswap_ask0
    self.start_uniswap_bid0 = uniswap_bid0
    self.start_binance_ask0 = binance_ask0
    self.start_binance_bid0 = binance_bid0

  def process_new_tick(self, uniswap_ts, uniswap_ask0, uniswap_bid0, binance_ask0, binance_bid0):
    if uniswap_ts == self.last_processed_uniswap_ts:
      return
    self.last_processed_uniswap_ts = uniswap_ts
    self._logger.info('tick:%s' % (to_datetime(uniswap_ts)))
    if uniswap_ask0 < binance_bid0 - self._kprice_diff_bar:
      if self.plus_signal_cnt > 0:
        self.plus_signal_cnt += 1
        self.total_diff += binance_bid0 - uniswap_ask0
      else:
        self.process_current_signal()
        self.plus_signal_cnt = 1
        self.minus_signal_cnt = 0
        self.total_diff = binance_bid0 - uniswap_ask0
        self.set_start_info(uniswap_ts, uniswap_ask0, uniswap_bid0, binance_ask0, binance_bid0)
      self.set_last_info(uniswap_ts, uniswap_ask0, uniswap_bid0, binance_ask0, binance_bid0)
    elif uniswap_bid0 > binance_ask0 + self._kprice_diff_bar:
      if self.minus_signal_cnt > 0:
        self.minus_signal_cnt += 1
        self.total_diff += uniswap_bid0 - binance_ask0
      else:
        self.process_current_signal()
        self.minus_signal_cnt = 1
        self.plus_signal_cnt = 0
        self.total_diff = uniswap_bid0 - binance_ask0
        self.set_start_info(uniswap_ts, uniswap_ask0, uniswap_bid0, binance_ask0, binance_bid0)
      self.set_last_info(uniswap_ts, uniswap_ask0, uniswap_bid0, binance_ask0, binance_bid0)
    else:
      self.process_current_signal()
      self.minus_signal_cnt = 0
      self.plus_signal_cnt = 0
      self.total_diff = 0.0

  def on_book_process(self, product, book):
    if product.exchange == 'Uniswap':
      if abs(book.timestamp - self._last_binance_book_ts) <= self._kmax_diff_ms * 10 ** 6:
        self.process_new_tick(book.timestamp, book.ask0().price, book.bid0().price, self._last_binance_ask0, self._last_binance_bid0)

    elif product.exchange == 'Binance':
      if abs(book.timestamp - self._last_uniswap_book_ts) <= self._kmax_diff_ms * 10 ** 6:
        self.process_new_tick(self._last_uniswap_book_ts, self._last_uniswap_ask0, self._last_uniswap_bid0, book.ask0().price, book.bid0().price)

class SimpleAMMForValidatorSubStrategy(SingleProductSubStrategyAMMBase):
  def __init__(self, model_profile, logger, parent):
    super().__init__(model_profile, logger, parent) 
    self._kprice_diff_bar = 0.4
    self._is_current_op_provide = True
    self._last_op_ts = 0
    self._kmin_op_interval = 15000000000
    
    self._window_estimator = WindowEstimator(max_record_cnt=600, range_multiplier=4.0)
    self._mid_win_estimator = WindowEstimator(max_record_cnt=60, range_multiplier=4.0)
    self._kwindow_size_multiplier = 1.5
    self._withdraw_bias_calculator = BiasCalculator(max_record_cnt=60, withdraw_signal_bar=3.0 ** 2, provide_signal_bar=0.2 ** 2)
    #self._provide_bias_calculator = BiasCalculator(max_record_cnt=300, withdraw_signal_bar=3.0 ** 2, provide_signal_bar=0.2 ** 2)
    #self._bias_validator = BiasValidator(bias_group_ts_diff = 600 * 10 ** 9, bias_validate_ts_diff = 3600 * 10 ** 9, bias_bar = 4.0, price_diff_bar = 5.0)
    self._provide_bias_calculator = BiasCalculator(max_record_cnt=FLAGS.bias_record_cnt, withdraw_signal_bar=3.0 ** 2, provide_signal_bar=0.2 ** 2)
    #self._bias_validator = BiasValidator(bias_group_ts_diff = 600 * 10 ** 9, bias_validate_ts_diff = FLAGS.bias_validate_ts_diff, bias_bar = FLAGS.bias_bar, price_diff_bar = FLAGS.price_diff_bar)
    #self._bias_validator = BiasValidator2(bias_group_ts_diff = 600 * 10 ** 9, bias_validate_ts_diff = FLAGS.bias_validate_ts_diff, bias_bar = FLAGS.bias_bar, price_diff_bar = FLAGS.price_diff_bar)
    self._bias_validator = BiasValidator3(is_minus_bias=True, bias_group_ts_diff = 600 * 10 ** 9, bias_validate_ts_diff = FLAGS.bias_validate_ts_diff, bias_bar = FLAGS.bias_bar, leave_bias_bar=FLAGS.leave_bias_bar, price_diff_bar = FLAGS.price_diff_bar)
    self._bias_plus_validator = BiasValidator3(is_minus_bias=False, bias_group_ts_diff = 600 * 10 ** 9, bias_validate_ts_diff = FLAGS.bias_validate_ts_diff, bias_bar = 3.0, leave_bias_bar = 0.75, price_diff_bar = FLAGS.price_diff_bar)

    self._klp_fee_per_unit = 0.000381944
    self._klp_loss_adjust_factor = 0.26 / 0.001924
    self._has_lp_position = False
    self._cur_lp_enter_price = 0.0
    self._cur_lp_enter_amount = 0.0
    self._cur_lp_enter_base_amount = 0.0
    self._cur_lp_enter_quote_amount = 0.0
    self._cur_lp_price_lower = 0.0
    self._cur_lp_price_upper = 0.0
    self._cur_lp_fee_loss_multiplier = 0.0
    self._cur_lp_in_cnt = 0
    self._cur_lp_out_cnt = 0

    self._cur_lp_loss = 0.0
    self._total_lp_loss = 0.0

    self._cur_lp_fee = 0.0
    self._total_fee = 0.0
    self._total_net_profit = 0.0
    self._last_lp_out_percentage = 0.0

    self.last_processed_uniswap_ts = 0

    self._uniswap_tick_cnt = 0

    self._end_timestamp = to_timestamp_int(datetime.strptime("20230523T235958", "%Y%m%dT%H%M%S"))

  def reset_lp_data(self):
    self._has_lp_position = False
    self._cur_lp_enter_price = 0.0
    self._cur_lp_enter_amount = 0.0
    self._cur_lp_enter_base_amount = 0.0
    self._cur_lp_enter_quote_amount = 0.0
    self._cur_lp_price_lower = 0.0
    self._cur_lp_price_upper = 0.0
    self._cur_lp_fee_loss_multiplier = 0.0
    self._cur_lp_in_cnt = 0
    self._cur_lp_out_cnt = 0
    self._cur_lp_loss = 0.0
    self._cur_lp_fee = 0.0

  def print_lp_info(self):
    if not self._has_lp_position:
      return
    
    self._logger.info('current liquidity: enter price:%f, amount: %f, price lower:%f, price upper:%f, record in:%d, record out:%f' % (
            self._cur_lp_enter_price, self._cur_lp_enter_amount, self._cur_lp_price_lower, self._cur_lp_price_upper, self._cur_lp_in_cnt, self._cur_lp_out_cnt))
    self._logger.info('current status: current loss:%f, current fee: %f, total loss:%f, total fee:%f, total net profit:%f' % (
      self._cur_lp_loss, self._cur_lp_fee, self._total_lp_loss, self._total_fee, self._total_net_profit))

  def update_lp_data_after_withdraw(self, cur_price):
    if not self._has_lp_position:
      return
    
    self._total_lp_loss += self._cur_lp_loss
    self._total_fee += self._cur_lp_fee
    self._total_net_profit = self._total_fee - self._total_lp_loss
    if self._cur_lp_out_cnt + self._cur_lp_in_cnt == 0:
      self._last_lp_out_percentage = -1
    else:
      self._last_lp_out_percentage = (float)(self._cur_lp_out_cnt) / (float)(self._cur_lp_out_cnt + self._cur_lp_in_cnt)
    self._logger.info('withdraw liquidity: enter price:%f, quit price: %f, amount: %f, price lower:%f, price upper:%f, record span:%d, record out percentage:%f' % (
            self._cur_lp_enter_price, cur_price, self._cur_lp_enter_amount, self._cur_lp_price_lower, self._cur_lp_price_upper, self._cur_lp_in_cnt + self._cur_lp_out_cnt, self._last_lp_out_percentage))
    self._logger.info('current status: current loss:%f, current fee: %f, total loss:%f, total fee:%f, total net profit:%f' % (
      self._cur_lp_loss, self._cur_lp_fee, self._total_lp_loss, self._total_fee, self._total_net_profit))
    self.reset_lp_data()

  def update_lp_data_after_provide(self, cur_price, price_upper, price_lower, amount):
    if self._has_lp_position:
      return

    self._has_lp_position = True
    self._cur_lp_enter_price = cur_price
    self._cur_lp_enter_amount = amount
    self._cur_lp_enter_base_amount = amount / (cur_price * 2.0) 
    self._cur_lp_enter_quote_amount = amount / 2.0
    self._cur_lp_price_lower = price_lower
    self._cur_lp_price_upper = price_upper
    self._cur_lp_fee_loss_multiplier = 100.0 / (price_upper - price_lower)
    self._cur_lp_in_cnt = 0
    self._cur_lp_out_cnt = 0
    self._cur_lp_loss = 0.0
    self._cur_lp_fee = 0.0
    self._logger.info('provide liquidity: enter price:%f, amount: %f, price lower:%f, price upper:%f,' % (
      self._cur_lp_enter_price, self._cur_lp_enter_amount, self._cur_lp_price_lower, self._cur_lp_price_upper))

  def calculate_current_loss(self, cur_price):
    if not self._has_lp_position:
      return
    
    lp_quit_base_amount = self._cur_lp_enter_base_amount * math.sqrt(self._cur_lp_enter_price / cur_price)
    lp_quit_quote_amount = self._cur_lp_enter_quote_amount * math.sqrt(cur_price / self._cur_lp_enter_price)
    raw_loss = (self._cur_lp_enter_base_amount * cur_price + self._cur_lp_enter_quote_amount) - (lp_quit_base_amount * cur_price + lp_quit_quote_amount)
    self._cur_lp_loss = raw_loss * self._klp_loss_adjust_factor * self._cur_lp_fee_loss_multiplier

  def calculate_current_fee(self, cur_price):
    if not self._has_lp_position:
      return
    
    if cur_price < self._cur_lp_price_upper and cur_price > self._cur_lp_price_lower:
      self._cur_lp_in_cnt += 1
      self._cur_lp_fee += self._klp_fee_per_unit * self._cur_lp_fee_loss_multiplier
    else:
      self._cur_lp_out_cnt += 1

  def process_new_tick_bias(self, uniswap_ts, uniswap_price, binance_price):
    if uniswap_ts == self.last_processed_uniswap_ts:
      return
    self.last_processed_uniswap_ts = uniswap_ts
    self._provide_bias_calculator.insert_record(binance_price, uniswap_price)
    self._withdraw_bias_calculator.insert_record(binance_price, uniswap_price)
    #self._bias_validator.insert_bias(uniswap_ts, self._provide_bias_calculator.get_bias_val(), uniswap_price)
    self._bias_plus_validator.insert_bias(uniswap_ts, self._withdraw_bias_calculator.get_bias_val(), uniswap_price)
    self._provide_bias_calculator.print_info(self._logger)
    self._withdraw_bias_calculator.print_info(self._logger)
    if uniswap_ts > self._end_timestamp:
      #logging.info("bias validation result:%f, valid cnt:%d, all cnt:%d", self._bias_validator.get_valid_ratio(), self._bias_validator.get_valid_cnt(), self._bias_validator.get_all_cnt())
      logging.info("bias validation result:%f, valid cnt:%d, all cnt:%d", self._bias_plus_validator.get_valid_ratio(), self._bias_plus_validator.get_valid_cnt(), self._bias_plus_validator.get_all_cnt())

  def on_book_process(self, product, book):
    if product.exchange == 'Uniswap':
      if abs(book.timestamp - self._last_binance_book_ts) <= self._kmax_diff_ms * 10 ** 6:
        self.process_new_tick_bias(book.timestamp, (book.ask0().price + book.bid0().price) / 2.0, (self._last_binance_ask0 + self._last_binance_bid0) / 2.0)

    elif product.exchange == 'Binance':
      if abs(book.timestamp - self._last_uniswap_book_ts) <= self._kmax_diff_ms * 10 ** 6:
        self.process_new_tick_bias(self._last_uniswap_book_ts, (self._last_uniswap_ask0 + self._last_uniswap_bid0) / 2.0, (book.ask0().price + book.bid0().price) / 2.0)

    if product != self._trade_product:
      return
    
    self._uniswap_tick_cnt += 1
    askp, bidp = book.ask0().price, book.bid0().price
    midp = (float)(askp + bidp) / 2.0
    self._logger.info('mid price:%f' % (midp))
    #self._window_estimator.insert_record(midp) 
    #self._mid_win_estimator.insert_record(midp)
    #self._window_estimator.print_info(self._logger)
    #self._mid_win_estimator.print_info(self._logger)
    
    #if not self._window_estimator.has_enough_record() or not self._provide_bias_calculator.has_enough_record() or not self._withdraw_bias_calculator.has_enough_record():
    #  return
    if not self._provide_bias_calculator.has_enough_record() or not self._withdraw_bias_calculator.has_enough_record():
      return

class SimpleAMMV2GeneralSubStrategy(SingleProductSubStrategyAMMGeneralBase):
  def __init__(self, model_profile, logger, parent):
    super().__init__(model_profile, logger, parent)
    self._last_op_ts = 0
    self._kmin_op_interval = 40000000000

    self._window_estimator = WindowEstimator(max_record_cnt=3600, range_multiplier=2.0)
    self._mid_win_estimator = WindowEstimator(max_record_cnt=60, range_multiplier=2.0)
    self._kwindow_size_multiplier = 3.0
    self._normal_withdraw_bias_calculator = BiasCalculator(max_record_cnt=60, withdraw_signal_bar=1.2 ** 2, provide_signal_bar=0.2 ** 2)
    self._danger_withdraw_bias_calculator = BiasCalculator(max_record_cnt=180, withdraw_signal_bar=2.5 ** 2, provide_signal_bar=0.2 ** 2)
    self._mid_provide_bias_calculator = BiasCalculator(max_record_cnt=600, withdraw_signal_bar=2.5 ** 2, provide_signal_bar=0.2 ** 2)
    self._long_provide_bias_calculator = BiasCalculator(max_record_cnt=3600, withdraw_signal_bar=2.5 ** 2, provide_signal_bar=0.2 ** 2)

    self._klp_fee_per_unit = 0.000381944 / 2.0
    self._klp_loss_adjust_factor = (0.26 / 0.001924) / 2.0
    self._has_lp_position = False
    self._cur_lp_enter_price = 0.0
    self._cur_lp_enter_amount = 0.0
    self._cur_lp_enter_base_amount = 0.0
    self._cur_lp_enter_quote_amount = 0.0
    self._cur_lp_price_lower = 0.0
    self._cur_lp_price_upper = 0.0
    self._cur_lp_fee_loss_multiplier = 0.0
    self._cur_lp_in_cnt = 0
    self._cur_lp_out_cnt = 0

    self._cur_loss_per_unit = 0.0
    self._cur_lp_loss = 0.0
    self._total_lp_loss = 0.0

    self._cur_lp_fee = 0.0
    self._total_fee = 0.0
    self._total_net_profit = 0.0
    self._last_lp_out_percentage = 0.0

    self.last_processed_uniswap_ts = 0

    self._uniswap_tick_cnt = 0

    self._end_timestamp = to_timestamp_int(datetime.strptime("20230528T235958", "%Y%m%dT%H%M%S"))

    self._total_midp_bias = 0.0
    self._midp_bias_cnt = 0

  def reset_lp_data(self):
    self._has_lp_position = False
    self._cur_lp_enter_price = 0.0
    self._cur_lp_enter_amount = 0.0
    self._cur_lp_enter_base_amount = 0.0
    self._cur_lp_enter_quote_amount = 0.0
    self._cur_lp_price_lower = 0.0
    self._cur_lp_price_upper = 0.0
    self._cur_lp_fee_loss_multiplier = 0.0
    self._cur_lp_in_cnt = 0
    self._cur_lp_out_cnt = 0
    self._cur_lp_loss = 0.0
    self._cur_lp_fee = 0.0
    self._cur_loss_per_unit = 0.0

  def print_lp_info(self):
    if not self._has_lp_position:
      return
    
    self._logger.info('current liquidity: enter price:%f, amount: %f, price lower:%f, price upper:%f, record in:%d, record out:%f' % (
            self._cur_lp_enter_price, self._cur_lp_enter_amount, self._cur_lp_price_lower, self._cur_lp_price_upper, self._cur_lp_in_cnt, self._cur_lp_out_cnt))
    self._logger.info('current status: current loss:%f, current fee: %f, total loss:%f, total fee:%f, total net profit:%f' % (
      self._cur_lp_loss, self._cur_lp_fee, self._total_lp_loss, self._total_fee, self._total_net_profit))

  def update_lp_data_after_withdraw(self, cur_price):
    if not self._has_lp_position:
      return
    
    self._total_lp_loss += self._cur_lp_loss
    self._total_fee += self._cur_lp_fee
    self._total_net_profit = self._total_fee - self._total_lp_loss
    if self._cur_lp_out_cnt + self._cur_lp_in_cnt == 0:
      self._last_lp_out_percentage = -1
    else:
      self._last_lp_out_percentage = (float)(self._cur_lp_out_cnt) / (float)(self._cur_lp_out_cnt + self._cur_lp_in_cnt)
    self._logger.info('withdraw liquidity: enter price:%f, quit price: %f, amount: %f, price lower:%f, price upper:%f, record span:%d, record out percentage:%f' % (
            self._cur_lp_enter_price, cur_price, self._cur_lp_enter_amount, self._cur_lp_price_lower, self._cur_lp_price_upper, self._cur_lp_in_cnt + self._cur_lp_out_cnt, self._last_lp_out_percentage))
    self._logger.info('current status: current loss:%f, current fee: %f, total loss:%f, total fee:%f, total net profit:%f' % (
      self._cur_lp_loss, self._cur_lp_fee, self._total_lp_loss, self._total_fee, self._total_net_profit))
    self.reset_lp_data()

  def update_lp_data_after_provide(self, cur_price, price_upper, price_lower, amount):
    if self._has_lp_position:
      return

    self._has_lp_position = True
    self._cur_lp_enter_price = cur_price
    self._cur_lp_enter_amount = amount
    self._cur_lp_enter_base_amount = amount / (cur_price * 2.0) 
    self._cur_lp_enter_quote_amount = amount / 2.0
    self._cur_lp_price_lower = price_lower
    self._cur_lp_price_upper = price_upper
    self._cur_lp_fee_loss_multiplier = 100.0 / (price_upper - price_lower)
    self._cur_lp_in_cnt = 0
    self._cur_lp_out_cnt = 0
    self._cur_lp_loss = 0.0
    self._cur_lp_fee = 0.0
    self.calculate_current_loss(cur_price + 1.0)
    self._cur_loss_per_unit = self._cur_lp_loss
    self._logger.info('provide liquidity: enter price:%f, amount: %f, price lower:%f, price upper:%f, loss per unit:%f' % (
      self._cur_lp_enter_price, self._cur_lp_enter_amount, self._cur_lp_price_lower, self._cur_lp_price_upper, self._cur_loss_per_unit))

  def calculate_current_loss(self, cur_price):
    if not self._has_lp_position:
      return
    
    lp_quit_base_amount = self._cur_lp_enter_base_amount * math.sqrt(self._cur_lp_enter_price / cur_price)
    lp_quit_quote_amount = self._cur_lp_enter_quote_amount * math.sqrt(cur_price / self._cur_lp_enter_price)
    raw_loss = (self._cur_lp_enter_base_amount * cur_price + self._cur_lp_enter_quote_amount) - (lp_quit_base_amount * cur_price + lp_quit_quote_amount)
    self._cur_lp_loss = raw_loss * self._klp_loss_adjust_factor * self._cur_lp_fee_loss_multiplier

  def calculate_current_fee(self, cur_price):
    if not self._has_lp_position:
      return
    
    if cur_price < self._cur_lp_price_upper and cur_price > self._cur_lp_price_lower:
      self._cur_lp_in_cnt += 1
      self._cur_lp_fee += self._klp_fee_per_unit * (self._cur_lp_enter_amount / 10000.0) * self._cur_lp_fee_loss_multiplier
    else:
      self._cur_lp_out_cnt += 1

  def calculate_current_accept_win_size(self, cur_price):
    if not self._has_lp_position:
      return None
    
    accept_loss = self._cur_lp_fee * 0.8
    cur_price_diff = abs(cur_price - self._cur_lp_enter_price)
    accept_price_diff = math.sqrt(accept_loss / self._cur_loss_per_unit)
    return accept_price_diff

  def is_bias_real_withdraw(self, bias, cur_price):
    price_diff = cur_price - self._cur_lp_enter_price
    base_minus_bias_bar = 1.2 ** 2
    base_plus_bias_bar = 1.1 ** 2
    half_win_size = 30.0
    bias_bar_factor = 1.0
    if abs(price_diff) > 5.0 + 0.001:
      bias_bar_factor = (half_win_size - abs(price_diff)) / (half_win_size - 5.0)
      bias_bar_factor = max(0.0, bias_bar_factor)
    
    
    if bias < 0.0:
      #if bias < -0.25 and cur_price > self._cur_lp_enter_price:
      #  logging.info("high possibility to come back")
      #  return False
      bias_bar = base_minus_bias_bar * bias_bar_factor
      logging.info("real bias bar:%f", bias_bar)
      return bias < -bias_bar
    else:
      #if bias > 0.25 and cur_price < self._cur_lp_enter_price:
      #  logging.info("high possibility to come back")
      #  return False
      bias_bar = base_plus_bias_bar * bias_bar_factor
      logging.info("real bias bar:%f", bias_bar)
      return bias > bias_bar

  def process_new_tick_bias(self, uniswap_ts, uniswap_price, binance_price):
    if uniswap_ts == self.last_processed_uniswap_ts:
      return
    self.last_processed_uniswap_ts = uniswap_ts
    self._mid_provide_bias_calculator.insert_record(binance_price, uniswap_price)
    self._long_provide_bias_calculator.insert_record(binance_price, uniswap_price)
    self._normal_withdraw_bias_calculator.insert_record(binance_price, uniswap_price)
    self._danger_withdraw_bias_calculator.insert_record(binance_price, uniswap_price)
    self._mid_provide_bias_calculator.print_info(self._logger)
    self._long_provide_bias_calculator.print_info(self._logger)
    self._normal_withdraw_bias_calculator.print_info(self._logger)
    self._danger_withdraw_bias_calculator.print_info(self._logger)

  def on_book_process(self, product, book):
    if product != self._trade_product:
      return
    
    self._uniswap_tick_cnt += 1
    if self._uniswap_tick_cnt < 5:
      return
    
    askp, bidp = book.ask0().price, book.bid0().price
    midp = (float)(askp + bidp) / 2.0
    self._logger.info('mid price:%f' % (midp))

    mid_usdc_usdt = (self._last_book_dict['Binance']['USDC-USDT'].ask0 + self._last_book_dict['Binance']['USDC-USDT'].bid0) / 2.0
    binance_ask0 = self._last_book_dict['Binance']['ETH-USDT'].ask0 / mid_usdc_usdt
    binance_bid0 = self._last_book_dict['Binance']['ETH-USDT'].bid0 / mid_usdc_usdt
    self._logger.info("binance calculate: ask0:%f, bid0:%f." % (binance_ask0, binance_bid0))

    self.process_new_tick_bias(book.timestamp, midp, (binance_ask0 + binance_bid0) / 2.0)
    self._window_estimator.insert_record(midp) 
    self._mid_win_estimator.insert_record(midp)
    #self._window_estimator.print_info(self._logger)
    #self._mid_win_estimator.print_info(self._logger)

    if not self._window_estimator.has_enough_record() or not self._mid_win_estimator.has_enough_record() \
    or not self._mid_provide_bias_calculator.has_enough_record() or not self._long_provide_bias_calculator.has_enough_record() \
    or not self._normal_withdraw_bias_calculator.has_enough_record() or not self._danger_withdraw_bias_calculator.has_enough_record():
      return

    if book.timestamp - self._last_op_ts < self._kmin_op_interval:
      return

    if not self._has_lp_position:
      avg_price = self._window_estimator.get_avg_price()
      half_window_size = self._window_estimator.get_half_window_size() * self._kwindow_size_multiplier
      if avg_price < 1.0e-6 or half_window_size < 1.0e-6:
        return

      if not self._mid_provide_bias_calculator.should_provide() or not self._long_provide_bias_calculator.should_provide() \
      or not self._normal_withdraw_bias_calculator.should_provide():
        return

      half_window_size = max(half_window_size, 75.0)
      price_upper = midp + half_window_size
      price_lower = max(midp - half_window_size, 0.01)

      self._last_op_ts = book.timestamp
      self.update_lp_data_after_provide(midp, price_upper, price_lower, 10000.0)
    else:
      self.calculate_current_fee(midp)
      self.calculate_current_loss(midp)
      if self._uniswap_tick_cnt % 1800 == 0:
        self.print_lp_info()
      accept_price_diff = self.calculate_current_accept_win_size(self._mid_win_estimator.get_avg_price())
      self._logger.info('accept price diff:%f' % (accept_price_diff))
      accept_price_diff = max(5.0, accept_price_diff)
      need_withdraw = self.is_bias_real_withdraw(self._normal_withdraw_bias_calculator.get_bias_val(), midp)
      if need_withdraw and abs(midp - self._cur_lp_enter_price) < accept_price_diff \
      or self._window_estimator.get_avg_price() < self._cur_lp_price_lower - 10.0 or self._window_estimator.get_avg_price() > self._cur_lp_price_upper + 10.0:
      #if self._normal_withdraw_bias_calculator.is_minus_bias_withdraw() and abs(midp - self._cur_lp_enter_price) < accept_price_diff \
      #or self._normal_withdraw_bias_calculator.is_plus_bias_withdraw() and abs(midp - self._cur_lp_enter_price) < accept_price_diff \
      #or self._window_estimator.get_avg_price() < self._cur_lp_price_lower - 10.0 or self._window_estimator.get_avg_price() > self._cur_lp_price_upper + 10.0:
      #or self._danger_withdraw_bias_calculator.is_minus_bias_withdraw() and midp < self._cur_lp_enter_price \
      #or self._danger_withdraw_bias_calculator.is_plus_bias_withdraw() and midp > self._cur_lp_enter_price \
      #or abs(midp - self._cur_lp_enter_price) > 200.0:
      #if self._withdraw_bias_calculator.is_minus_bias_withdraw() and midp < self._cur_lp_enter_price \
      #or self._withdraw_bias_calculator.is_plus_bias_withdraw() and midp > self._cur_lp_enter_price \
      #or self._window_estimator.get_avg_price() < self._cur_lp_price_lower or self._window_estimator.get_avg_price() > self._cur_lp_price_upper:
      #or abs(self._mid_win_estimator.get_avg_price() - self._window_estimator.get_avg_price()) > 3.0 and abs(midp - self._cur_lp_enter_price) < 5.0 \
      #or new_win_lower < self._cur_lp_price_lower or new_win_upper > self._cur_lp_price_upper:
      #or abs(midp - self._cur_lp_enter_price) > 100.0:
        self._last_op_ts = book.timestamp
        self.update_lp_data_after_withdraw(midp)

  def on_book_process_bias_stat(self, product, book):
    if product != self._trade_product:
      return

    self._uniswap_tick_cnt += 1
    if self._uniswap_tick_cnt < 5:
      return
    
    mid_usdc_usdt = (self._last_book_dict['Binance']['USDC-USDT'].ask0 + self._last_book_dict['Binance']['USDC-USDT'].bid0) / 2.0
    binance_ask0 = self._last_book_dict['Binance']['ETH-USDT'].ask0 / mid_usdc_usdt
    binance_bid0 = self._last_book_dict['Binance']['ETH-USDT'].bid0 / mid_usdc_usdt

    self._logger.info("From calculate: ask0:%f, bid0:%f. From feed: ask0:%f, bid0:%f" % (
      binance_ask0, binance_bid0, self._last_book_dict['Binance']['ETH-USDC'].ask0, self._last_book_dict['Binance']['ETH-USDC'].bid0
    ))

    if abs(binance_ask0 - self._last_book_dict['Binance']['ETH-USDC'].ask0) > 1.0 \
    or abs(binance_bid0 - self._last_book_dict['Binance']['ETH-USDC'].bid0) > 1.0:
      self._logger.info("Large diff!!")
    
    midp_calculated = (binance_ask0 + binance_bid0) / 2.0
    midp_feed = (self._last_book_dict['Binance']['ETH-USDC'].ask0 + self._last_book_dict['Binance']['ETH-USDC'].bid0) / 2.0
    midp_bias = midp_calculated - midp_feed
    self._total_midp_bias += midp_bias
    self._midp_bias_cnt += 1
    self._logger.info('midp bias:%f, avg midp bias:%f' % (midp_bias, self._total_midp_bias / float(self._midp_bias_cnt)))

  def on_book_process2(self, product, book):
    if product.exchange == 'Uniswap':
      if abs(book.timestamp - self._last_binance_book_ts) <= self._kmax_diff_ms * 10 ** 6:
        self.process_new_tick_bias(book.timestamp, (book.ask0().price + book.bid0().price) / 2.0, (self._last_binance_ask0 + self._last_binance_bid0) / 2.0)

    elif product.exchange == 'Binance':
      if abs(book.timestamp - self._last_uniswap_book_ts) <= self._kmax_diff_ms * 10 ** 6:
        self.process_new_tick_bias(self._last_uniswap_book_ts, (self._last_uniswap_ask0 + self._last_uniswap_bid0) / 2.0, (book.ask0().price + book.bid0().price) / 2.0)

    if product != self._trade_product:
      return
    
    self._uniswap_tick_cnt += 1
    askp, bidp = book.ask0().price, book.bid0().price
    midp = (float)(askp + bidp) / 2.0
    self._logger.info('mid price:%f' % (midp))
    self._window_estimator.insert_record(midp) 
    self._mid_win_estimator.insert_record(midp)
    #self._window_estimator.print_info(self._logger)
    #self._mid_win_estimator.print_info(self._logger)

    if not self._window_estimator.has_enough_record() or not self._mid_win_estimator.has_enough_record() \
    or not self._mid_provide_bias_calculator.has_enough_record() or not self._long_provide_bias_calculator.has_enough_record() \
    or not self._normal_withdraw_bias_calculator.has_enough_record() or not self._danger_withdraw_bias_calculator.has_enough_record():
      return

    if book.timestamp - self._last_op_ts < self._kmin_op_interval:
      return

    if not self._has_lp_position:
      avg_price = self._window_estimator.get_avg_price()
      half_window_size = self._window_estimator.get_half_window_size() * self._kwindow_size_multiplier
      if avg_price < 1.0e-6 or half_window_size < 1.0e-6:
        return

      if not self._mid_provide_bias_calculator.should_provide() or not self._long_provide_bias_calculator.should_provide() \
      or not self._normal_withdraw_bias_calculator.should_provide():
        return

      half_window_size = max(half_window_size, 75.0)
      price_upper = midp + half_window_size
      price_lower = max(midp - half_window_size, 0.01)

      self._last_op_ts = book.timestamp
      self.update_lp_data_after_provide(midp, price_upper, price_lower, 10000.0)
    else:
      self.calculate_current_fee(midp)
      self.calculate_current_loss(midp)
      if self._uniswap_tick_cnt % 1800 == 0:
        self.print_lp_info()
      accept_price_diff = self.calculate_current_accept_win_size(self._mid_win_estimator.get_avg_price())
      self._logger.info('accept price diff:%f' % (accept_price_diff))
      accept_price_diff = max(5.0, accept_price_diff)
      need_withdraw = self.is_bias_real_withdraw(self._normal_withdraw_bias_calculator.get_bias_val(), midp)
      if need_withdraw and abs(midp - self._cur_lp_enter_price) < accept_price_diff \
      or self._window_estimator.get_avg_price() < self._cur_lp_price_lower - 10.0 or self._window_estimator.get_avg_price() > self._cur_lp_price_upper + 10.0:
      #if self._normal_withdraw_bias_calculator.is_minus_bias_withdraw() and abs(midp - self._cur_lp_enter_price) < accept_price_diff \
      #or self._normal_withdraw_bias_calculator.is_plus_bias_withdraw() and abs(midp - self._cur_lp_enter_price) < accept_price_diff \
      #or self._window_estimator.get_avg_price() < self._cur_lp_price_lower - 10.0 or self._window_estimator.get_avg_price() > self._cur_lp_price_upper + 10.0:
      #or self._danger_withdraw_bias_calculator.is_minus_bias_withdraw() and midp < self._cur_lp_enter_price \
      #or self._danger_withdraw_bias_calculator.is_plus_bias_withdraw() and midp > self._cur_lp_enter_price \
      #or abs(midp - self._cur_lp_enter_price) > 200.0:
      #if self._withdraw_bias_calculator.is_minus_bias_withdraw() and midp < self._cur_lp_enter_price \
      #or self._withdraw_bias_calculator.is_plus_bias_withdraw() and midp > self._cur_lp_enter_price \
      #or self._window_estimator.get_avg_price() < self._cur_lp_price_lower or self._window_estimator.get_avg_price() > self._cur_lp_price_upper:
      #or abs(self._mid_win_estimator.get_avg_price() - self._window_estimator.get_avg_price()) > 3.0 and abs(midp - self._cur_lp_enter_price) < 5.0 \
      #or new_win_lower < self._cur_lp_price_lower or new_win_upper > self._cur_lp_price_upper:
      #or abs(midp - self._cur_lp_enter_price) > 100.0:
        self._last_op_ts = book.timestamp
        self.update_lp_data_after_withdraw(midp)

class SimpleAMMV2SubStrategy(SingleProductSubStrategyAMMBase):
  def __init__(self, model_profile, logger, parent):
    super().__init__(model_profile, logger, parent)
    self._last_op_ts = 0
    self._kmin_op_interval = 40000000000
    
    # for on_book_process1
    '''
    self._window_estimator = WindowEstimator(max_record_cnt=3600, range_multiplier=2.0)
    self._mid_win_estimator = WindowEstimator(max_record_cnt=600, range_multiplier=2.0)
    self._kwindow_size_multiplier = 3.0
    self._withdraw_bias_calculator = BiasCalculator(max_record_cnt=60, withdraw_signal_bar=1.2 ** 2, provide_signal_bar=0.2 ** 2)
    self._provide_bias_calculator = BiasCalculator(max_record_cnt=300, withdraw_signal_bar=2.5 ** 2, provide_signal_bar=0.2 ** 2)
    '''

    self._window_estimator = WindowEstimator(max_record_cnt=3600, range_multiplier=2.0)
    self._mid_win_estimator = WindowEstimator(max_record_cnt=60, range_multiplier=2.0)
    self._kwindow_size_multiplier = 3.0
    self._normal_withdraw_bias_calculator = BiasCalculator(max_record_cnt=60, withdraw_signal_bar=1.2 ** 2, provide_signal_bar=0.2 ** 2)
    self._danger_withdraw_bias_calculator = BiasCalculator(max_record_cnt=180, withdraw_signal_bar=2.5 ** 2, provide_signal_bar=0.2 ** 2)
    self._mid_provide_bias_calculator = BiasCalculator(max_record_cnt=600, withdraw_signal_bar=2.5 ** 2, provide_signal_bar=0.2 ** 2)
    self._long_provide_bias_calculator = BiasCalculator(max_record_cnt=3600, withdraw_signal_bar=2.5 ** 2, provide_signal_bar=0.2 ** 2)

    self._klp_fee_per_unit = 0.000381944 / 2.0
    self._klp_loss_adjust_factor = (0.26 / 0.001924) / 2.0
    self._has_lp_position = False
    self._cur_lp_enter_price = 0.0
    self._cur_lp_enter_amount = 0.0
    self._cur_lp_enter_base_amount = 0.0
    self._cur_lp_enter_quote_amount = 0.0
    self._cur_lp_price_lower = 0.0
    self._cur_lp_price_upper = 0.0
    self._cur_lp_fee_loss_multiplier = 0.0
    self._cur_lp_in_cnt = 0
    self._cur_lp_out_cnt = 0

    self._cur_loss_per_unit = 0.0
    self._cur_lp_loss = 0.0
    self._total_lp_loss = 0.0

    self._cur_lp_fee = 0.0
    self._total_fee = 0.0
    self._total_net_profit = 0.0
    self._last_lp_out_percentage = 0.0

    self.last_processed_uniswap_ts = 0

    self._uniswap_tick_cnt = 0

    self._end_timestamp = to_timestamp_int(datetime.strptime("20230528T235958", "%Y%m%dT%H%M%S"))

  def reset_lp_data(self):
    self._has_lp_position = False
    self._cur_lp_enter_price = 0.0
    self._cur_lp_enter_amount = 0.0
    self._cur_lp_enter_base_amount = 0.0
    self._cur_lp_enter_quote_amount = 0.0
    self._cur_lp_price_lower = 0.0
    self._cur_lp_price_upper = 0.0
    self._cur_lp_fee_loss_multiplier = 0.0
    self._cur_lp_in_cnt = 0
    self._cur_lp_out_cnt = 0
    self._cur_lp_loss = 0.0
    self._cur_lp_fee = 0.0
    self._cur_loss_per_unit = 0.0

  def print_lp_info(self):
    if not self._has_lp_position:
      return
    
    self._logger.info('current liquidity: enter price:%f, amount: %f, price lower:%f, price upper:%f, record in:%d, record out:%f' % (
            self._cur_lp_enter_price, self._cur_lp_enter_amount, self._cur_lp_price_lower, self._cur_lp_price_upper, self._cur_lp_in_cnt, self._cur_lp_out_cnt))
    self._logger.info('current status: current loss:%f, current fee: %f, total loss:%f, total fee:%f, total net profit:%f' % (
      self._cur_lp_loss, self._cur_lp_fee, self._total_lp_loss, self._total_fee, self._total_net_profit))

  def update_lp_data_after_withdraw(self, cur_price):
    if not self._has_lp_position:
      return
    
    self._total_lp_loss += self._cur_lp_loss
    self._total_fee += self._cur_lp_fee
    self._total_net_profit = self._total_fee - self._total_lp_loss
    if self._cur_lp_out_cnt + self._cur_lp_in_cnt == 0:
      self._last_lp_out_percentage = -1
    else:
      self._last_lp_out_percentage = (float)(self._cur_lp_out_cnt) / (float)(self._cur_lp_out_cnt + self._cur_lp_in_cnt)
    self._logger.info('withdraw liquidity: enter price:%f, quit price: %f, amount: %f, price lower:%f, price upper:%f, record span:%d, record out percentage:%f' % (
            self._cur_lp_enter_price, cur_price, self._cur_lp_enter_amount, self._cur_lp_price_lower, self._cur_lp_price_upper, self._cur_lp_in_cnt + self._cur_lp_out_cnt, self._last_lp_out_percentage))
    self._logger.info('current status: current loss:%f, current fee: %f, total loss:%f, total fee:%f, total net profit:%f' % (
      self._cur_lp_loss, self._cur_lp_fee, self._total_lp_loss, self._total_fee, self._total_net_profit))
    self.reset_lp_data()

  def update_lp_data_after_provide(self, cur_price, price_upper, price_lower, amount):
    if self._has_lp_position:
      return

    self._has_lp_position = True
    self._cur_lp_enter_price = cur_price
    self._cur_lp_enter_amount = amount
    self._cur_lp_enter_base_amount = amount / (cur_price * 2.0) 
    self._cur_lp_enter_quote_amount = amount / 2.0
    self._cur_lp_price_lower = price_lower
    self._cur_lp_price_upper = price_upper
    self._cur_lp_fee_loss_multiplier = 100.0 / (price_upper - price_lower)
    self._cur_lp_in_cnt = 0
    self._cur_lp_out_cnt = 0
    self._cur_lp_loss = 0.0
    self._cur_lp_fee = 0.0
    self.calculate_current_loss(cur_price + 1.0)
    self._cur_loss_per_unit = self._cur_lp_loss
    self._logger.info('provide liquidity: enter price:%f, amount: %f, price lower:%f, price upper:%f, loss per unit:%f' % (
      self._cur_lp_enter_price, self._cur_lp_enter_amount, self._cur_lp_price_lower, self._cur_lp_price_upper, self._cur_loss_per_unit))

  def calculate_current_loss(self, cur_price):
    if not self._has_lp_position:
      return
    
    lp_quit_base_amount = self._cur_lp_enter_base_amount * math.sqrt(self._cur_lp_enter_price / cur_price)
    lp_quit_quote_amount = self._cur_lp_enter_quote_amount * math.sqrt(cur_price / self._cur_lp_enter_price)
    raw_loss = (self._cur_lp_enter_base_amount * cur_price + self._cur_lp_enter_quote_amount) - (lp_quit_base_amount * cur_price + lp_quit_quote_amount)
    self._cur_lp_loss = raw_loss * self._klp_loss_adjust_factor * self._cur_lp_fee_loss_multiplier

  def calculate_current_fee(self, cur_price):
    if not self._has_lp_position:
      return
    
    if cur_price < self._cur_lp_price_upper and cur_price > self._cur_lp_price_lower:
      self._cur_lp_in_cnt += 1
      self._cur_lp_fee += self._klp_fee_per_unit * (self._cur_lp_enter_amount / 10000.0) * self._cur_lp_fee_loss_multiplier
    else:
      self._cur_lp_out_cnt += 1

  def calculate_current_accept_win_size(self, cur_price):
    if not self._has_lp_position:
      return None
    
    accept_loss = self._cur_lp_fee * 0.8
    cur_price_diff = abs(cur_price - self._cur_lp_enter_price)
    accept_price_diff = math.sqrt(accept_loss / self._cur_loss_per_unit)
    return accept_price_diff

  def is_bias_real_withdraw(self, bias, cur_price):
    price_diff = cur_price - self._cur_lp_enter_price
    base_minus_bias_bar = 1.2 ** 2
    base_plus_bias_bar = 1.1 ** 2
    #base_minus_bias_bar = 1.2
    #base_plus_bias_bar = 1.1
    half_win_size = 30.0
    bias_bar_factor = 1.0
    if abs(price_diff) > 5.0 + 0.001:
      bias_bar_factor = (half_win_size - abs(price_diff)) / (half_win_size - 5.0)
      bias_bar_factor = max(0.0, bias_bar_factor)
    
    
    if bias < 0.0:
      #if bias < -0.25 and cur_price > self._cur_lp_enter_price:
      #  logging.info("high possibility to come back")
      #  return False
      bias_bar = base_minus_bias_bar * bias_bar_factor
      #bias_bar = (base_minus_bias_bar * bias_bar_factor) ** 2
      logging.info("real bias bar:%f", bias_bar)
      return bias < -bias_bar
    else:
      #if bias > 0.25 and cur_price < self._cur_lp_enter_price:
      #  logging.info("high possibility to come back")
      #  return False
      bias_bar = base_plus_bias_bar * bias_bar_factor
      #bias_bar = (base_plus_bias_bar * bias_bar_factor) ** 2
      logging.info("real bias bar:%f", bias_bar)
      return bias > bias_bar

  def process_new_tick_bias(self, uniswap_ts, uniswap_price, binance_price):
    if uniswap_ts == self.last_processed_uniswap_ts:
      return
    self.last_processed_uniswap_ts = uniswap_ts
    self._mid_provide_bias_calculator.insert_record(binance_price, uniswap_price)
    self._long_provide_bias_calculator.insert_record(binance_price, uniswap_price)
    self._normal_withdraw_bias_calculator.insert_record(binance_price, uniswap_price)
    self._danger_withdraw_bias_calculator.insert_record(binance_price, uniswap_price)
    self._mid_provide_bias_calculator.print_info(self._logger)
    self._long_provide_bias_calculator.print_info(self._logger)
    self._normal_withdraw_bias_calculator.print_info(self._logger)
    self._danger_withdraw_bias_calculator.print_info(self._logger)

  def on_book_process(self, product, book):
    if product.exchange == 'Uniswap':
      if abs(book.timestamp - self._last_binance_book_ts) <= self._kmax_diff_ms * 10 ** 6:
        self.process_new_tick_bias(book.timestamp, (book.ask0().price + book.bid0().price) / 2.0, (self._last_binance_ask0 + self._last_binance_bid0) / 2.0)

    elif product.exchange == 'Binance':
      if abs(book.timestamp - self._last_uniswap_book_ts) <= self._kmax_diff_ms * 10 ** 6:
        self.process_new_tick_bias(self._last_uniswap_book_ts, (self._last_uniswap_ask0 + self._last_uniswap_bid0) / 2.0, (book.ask0().price + book.bid0().price) / 2.0)

    if product != self._trade_product:
      return
    
    self._uniswap_tick_cnt += 1
    askp, bidp = book.ask0().price, book.bid0().price
    midp = (float)(askp + bidp) / 2.0
    self._logger.info('mid price:%f' % (midp))
    self._window_estimator.insert_record(midp) 
    self._mid_win_estimator.insert_record(midp)
    #self._window_estimator.print_info(self._logger)
    #self._mid_win_estimator.print_info(self._logger)

    if not self._window_estimator.has_enough_record() or not self._mid_win_estimator.has_enough_record() \
    or not self._mid_provide_bias_calculator.has_enough_record() or not self._long_provide_bias_calculator.has_enough_record() \
    or not self._normal_withdraw_bias_calculator.has_enough_record() or not self._danger_withdraw_bias_calculator.has_enough_record():
      return

    if book.timestamp - self._last_op_ts < self._kmin_op_interval:
      return

    if not self._has_lp_position:
      avg_price = self._window_estimator.get_avg_price()
      half_window_size = self._window_estimator.get_half_window_size() * self._kwindow_size_multiplier
      if avg_price < 1.0e-6 or half_window_size < 1.0e-6:
        return

      if not self._mid_provide_bias_calculator.should_provide() or not self._long_provide_bias_calculator.should_provide() \
      or not self._normal_withdraw_bias_calculator.should_provide():
        return

      half_window_size = max(half_window_size, 75.0)
      price_upper = midp + half_window_size
      price_lower = max(midp - half_window_size, 0.01)

      self._last_op_ts = book.timestamp
      self.update_lp_data_after_provide(midp, price_upper, price_lower, 10000.0)
    else:
      self.calculate_current_fee(midp)
      self.calculate_current_loss(midp)
      if self._uniswap_tick_cnt % 1800 == 0:
        self.print_lp_info()
      accept_price_diff = self.calculate_current_accept_win_size(self._mid_win_estimator.get_avg_price())
      self._logger.info('accept price diff:%f' % (accept_price_diff))
      accept_price_diff = max(5.0, accept_price_diff)
      need_withdraw = self.is_bias_real_withdraw(self._normal_withdraw_bias_calculator.get_bias_val(), midp)
      if need_withdraw and abs(midp - self._cur_lp_enter_price) < accept_price_diff \
      or self._window_estimator.get_avg_price() < self._cur_lp_price_lower - 10.0 or self._window_estimator.get_avg_price() > self._cur_lp_price_upper + 10.0:
      #if self._normal_withdraw_bias_calculator.is_minus_bias_withdraw() and abs(midp - self._cur_lp_enter_price) < accept_price_diff \
      #or self._normal_withdraw_bias_calculator.is_plus_bias_withdraw() and abs(midp - self._cur_lp_enter_price) < accept_price_diff \
      #or self._window_estimator.get_avg_price() < self._cur_lp_price_lower - 10.0 or self._window_estimator.get_avg_price() > self._cur_lp_price_upper + 10.0:
      #or self._danger_withdraw_bias_calculator.is_minus_bias_withdraw() and midp < self._cur_lp_enter_price \
      #or self._danger_withdraw_bias_calculator.is_plus_bias_withdraw() and midp > self._cur_lp_enter_price \
      #or abs(midp - self._cur_lp_enter_price) > 200.0:
      #if self._withdraw_bias_calculator.is_minus_bias_withdraw() and midp < self._cur_lp_enter_price \
      #or self._withdraw_bias_calculator.is_plus_bias_withdraw() and midp > self._cur_lp_enter_price \
      #or self._window_estimator.get_avg_price() < self._cur_lp_price_lower or self._window_estimator.get_avg_price() > self._cur_lp_price_upper:
      #or abs(self._mid_win_estimator.get_avg_price() - self._window_estimator.get_avg_price()) > 3.0 and abs(midp - self._cur_lp_enter_price) < 5.0 \
      #or new_win_lower < self._cur_lp_price_lower or new_win_upper > self._cur_lp_price_upper:
      #or abs(midp - self._cur_lp_enter_price) > 100.0:
        self._last_op_ts = book.timestamp
        self.update_lp_data_after_withdraw(midp)

  def on_book_process1(self, product, book):
    if product.exchange == 'Uniswap':
      if abs(book.timestamp - self._last_binance_book_ts) <= self._kmax_diff_ms * 10 ** 6:
        self.process_new_tick_bias(book.timestamp, (book.ask0().price + book.bid0().price) / 2.0, (self._last_binance_ask0 + self._last_binance_bid0) / 2.0)

    elif product.exchange == 'Binance':
      if abs(book.timestamp - self._last_uniswap_book_ts) <= self._kmax_diff_ms * 10 ** 6:
        self.process_new_tick_bias(self._last_uniswap_book_ts, (self._last_uniswap_ask0 + self._last_uniswap_bid0) / 2.0, (book.ask0().price + book.bid0().price) / 2.0)

    if product != self._trade_product:
      return
    
    self._uniswap_tick_cnt += 1
    askp, bidp = book.ask0().price, book.bid0().price
    midp = (float)(askp + bidp) / 2.0
    self._logger.info('mid price:%f' % (midp))
    self._window_estimator.insert_record(midp) 
    self._mid_win_estimator.insert_record(midp)
    #self._window_estimator.print_info(self._logger)
    #self._mid_win_estimator.print_info(self._logger)
    
    if not self._window_estimator.has_enough_record() or not self._mid_win_estimator.has_enough_record() or not self._provide_bias_calculator.has_enough_record() or not self._withdraw_bias_calculator.has_enough_record():
      return

    if book.timestamp - self._last_op_ts < self._kmin_op_interval:
      return

    if not self._has_lp_position:
      avg_price = self._window_estimator.get_avg_price()
      half_window_size = self._window_estimator.get_half_window_size() * self._kwindow_size_multiplier
      if avg_price < 1.0e-6 or half_window_size < 1.0e-6:
        return
      #if not self._provide_bias_calculator.should_provide() or not self._withdraw_bias_calculator.should_provide() or abs(self._mid_win_estimator.get_avg_price() - self._window_estimator.get_avg_price()) > 2.0:
      #  return
      if not self._provide_bias_calculator.should_provide() or not self._withdraw_bias_calculator.should_provide():
        return

      half_window_size = max(half_window_size, 50.0)
      price_upper = midp + half_window_size
      price_lower = max(midp - half_window_size, 0.01)

      self._last_op_ts = book.timestamp
      self.update_lp_data_after_provide(midp, price_upper, price_lower, 10000.0)
    else:
      self.calculate_current_fee(midp)
      self.calculate_current_loss(midp)
      if self._uniswap_tick_cnt % 1800 == 0:
        self.print_lp_info()
      window_lower, window_upper = self._window_estimator.get_price_window()
      if window_lower < 1.0e-6 or window_upper < 1.0e-6 or window_upper < window_lower:
        return
      new_win_lower = window_lower + (window_upper - window_lower) / 4.0
      new_win_upper = window_upper - (window_upper - window_lower) / 4.0
      window_upper = new_win_upper
      window_lower = new_win_lower
      if self._withdraw_bias_calculator.is_minus_bias_withdraw() and abs(midp - self._cur_lp_enter_price) < 5.0 \
      or self._withdraw_bias_calculator.is_plus_bias_withdraw() and abs(midp - self._cur_lp_enter_price) < 5.0:
      #or abs(midp - self._cur_lp_enter_price) > 200.0:
      #if self._withdraw_bias_calculator.is_minus_bias_withdraw() and midp < self._cur_lp_enter_price \
      #or self._withdraw_bias_calculator.is_plus_bias_withdraw() and midp > self._cur_lp_enter_price \
      #or self._window_estimator.get_avg_price() < self._cur_lp_price_lower or self._window_estimator.get_avg_price() > self._cur_lp_price_upper:
      #or abs(self._mid_win_estimator.get_avg_price() - self._window_estimator.get_avg_price()) > 3.0 and abs(midp - self._cur_lp_enter_price) < 5.0 \
      #or new_win_lower < self._cur_lp_price_lower or new_win_upper > self._cur_lp_price_upper:
      #or abs(midp - self._cur_lp_enter_price) > 100.0:
        self._last_op_ts = book.timestamp
        self.update_lp_data_after_withdraw(midp)

class SimpleAMMV3SubStrategy(SingleProductSubStrategyAMMBase):
  def __init__(self, model_profile, logger, parent):
    super().__init__(model_profile, logger, parent)
    self._last_op_ts = 0
    self._kmin_op_interval = 40000000000

    self._window_estimator = WindowEstimator(max_record_cnt=3600, range_multiplier=2.0)
    self._mid_win_estimator = WindowEstimator(max_record_cnt=60, range_multiplier=2.0)
    self._kwindow_size_multiplier = 3.0
    self._normal_withdraw_bias_calculator = BiasCalculator(max_record_cnt=60, withdraw_signal_bar=1.2 ** 2, provide_signal_bar=0.3 ** 2)
    self._danger_withdraw_bias_calculator = BiasCalculator(max_record_cnt=180, withdraw_signal_bar=2.5 ** 2, provide_signal_bar=0.2 ** 2)
    self._mid_provide_bias_calculator = BiasCalculator(max_record_cnt=600, withdraw_signal_bar=2.5 ** 2, provide_signal_bar=0.3 ** 2)
    self._long_provide_bias_calculator = BiasCalculator(max_record_cnt=3600, withdraw_signal_bar=2.5 ** 2, provide_signal_bar=0.3 ** 2)

    #self._klp_fee_per_unit = 0.000381944 / 2.0
    self._klp_fee_per_unit = 0.000381944
    self._klp_loss_adjust_factor = (0.26 / 0.001924) / 2.0
    self._has_lp_position = False
    self._cur_lp_enter_price = 0.0
    self._cur_lp_enter_amount = 0.0
    self._cur_lp_enter_base_amount = 0.0
    self._cur_lp_enter_quote_amount = 0.0
    self._cur_lp_price_lower = 0.0
    self._cur_lp_price_upper = 0.0
    self._cur_lp_fee_loss_multiplier = 0.0
    self._cur_lp_in_cnt = 0
    self._cur_lp_out_cnt = 0

    self._cur_loss_per_unit = 0.0
    self._cur_lp_loss = 0.0
    self._total_lp_loss = 0.0

    self._cur_lp_fee = 0.0
    self._total_fee = 0.0
    self._total_net_profit = 0.0
    self._last_lp_out_percentage = 0.0

    self.last_processed_uniswap_ts = 0

    self._uniswap_tick_cnt = 0

    self._end_timestamp = to_timestamp_int(datetime.strptime("20230528T235958", "%Y%m%dT%H%M%S"))

  def reset_lp_data(self):
    self._has_lp_position = False
    self._cur_lp_enter_price = 0.0
    self._cur_lp_enter_amount = 0.0
    self._cur_lp_enter_base_amount = 0.0
    self._cur_lp_enter_quote_amount = 0.0
    self._cur_lp_price_lower = 0.0
    self._cur_lp_price_upper = 0.0
    self._cur_lp_fee_loss_multiplier = 0.0
    self._cur_lp_in_cnt = 0
    self._cur_lp_out_cnt = 0
    self._cur_lp_loss = 0.0
    self._cur_lp_fee = 0.0
    self._cur_loss_per_unit = 0.0

  def print_lp_info(self):
    if not self._has_lp_position:
      return
    
    self._logger.info('current liquidity: enter price:%f, amount: %f, price lower:%f, price upper:%f, record in:%d, record out:%f' % (
            self._cur_lp_enter_price, self._cur_lp_enter_amount, self._cur_lp_price_lower, self._cur_lp_price_upper, self._cur_lp_in_cnt, self._cur_lp_out_cnt))
    self._logger.info('current status: current loss:%f, current fee: %f, total loss:%f, total fee:%f, total net profit:%f' % (
      self._cur_lp_loss, self._cur_lp_fee, self._total_lp_loss, self._total_fee, self._total_net_profit))

  def update_lp_data_after_withdraw(self, cur_price):
    if not self._has_lp_position:
      return
    
    self._total_lp_loss += self._cur_lp_loss
    self._total_fee += self._cur_lp_fee
    self._total_net_profit = self._total_fee - self._total_lp_loss
    if self._cur_lp_out_cnt + self._cur_lp_in_cnt == 0:
      self._last_lp_out_percentage = -1
    else:
      self._last_lp_out_percentage = (float)(self._cur_lp_out_cnt) / (float)(self._cur_lp_out_cnt + self._cur_lp_in_cnt)
    self._logger.info('withdraw liquidity: enter price:%f, quit price: %f, amount: %f, price lower:%f, price upper:%f, record span:%d, record out percentage:%f' % (
            self._cur_lp_enter_price, cur_price, self._cur_lp_enter_amount, self._cur_lp_price_lower, self._cur_lp_price_upper, self._cur_lp_in_cnt + self._cur_lp_out_cnt, self._last_lp_out_percentage))
    self._logger.info('current status: current loss:%f, current fee: %f, total loss:%f, total fee:%f, total net profit:%f' % (
      self._cur_lp_loss, self._cur_lp_fee, self._total_lp_loss, self._total_fee, self._total_net_profit))
    self.reset_lp_data()

  def update_lp_data_after_provide(self, cur_price, price_upper, price_lower, amount):
    if self._has_lp_position:
      return

    self._has_lp_position = True
    self._cur_lp_enter_price = cur_price
    self._cur_lp_enter_amount = amount
    self._cur_lp_enter_base_amount = amount / (cur_price * 2.0) 
    self._cur_lp_enter_quote_amount = amount / 2.0
    self._cur_lp_price_lower = price_lower
    self._cur_lp_price_upper = price_upper
    self._cur_lp_fee_loss_multiplier = 100.0 / (price_upper - price_lower)
    self._cur_lp_in_cnt = 0
    self._cur_lp_out_cnt = 0
    self._cur_lp_loss = 0.0
    self._cur_lp_fee = 0.0
    self.calculate_current_loss(cur_price + 1.0)
    self._cur_loss_per_unit = self._cur_lp_loss
    self._logger.info('provide liquidity: enter price:%f, amount: %f, price lower:%f, price upper:%f, loss per unit:%f' % (
      self._cur_lp_enter_price, self._cur_lp_enter_amount, self._cur_lp_price_lower, self._cur_lp_price_upper, self._cur_loss_per_unit))

  def calculate_current_loss(self, cur_price):
    if not self._has_lp_position:
      return
    
    lp_quit_base_amount = self._cur_lp_enter_base_amount * math.sqrt(self._cur_lp_enter_price / cur_price)
    lp_quit_quote_amount = self._cur_lp_enter_quote_amount * math.sqrt(cur_price / self._cur_lp_enter_price)
    raw_loss = (self._cur_lp_enter_base_amount * cur_price + self._cur_lp_enter_quote_amount) - (lp_quit_base_amount * cur_price + lp_quit_quote_amount)
    self._cur_lp_loss = raw_loss * self._klp_loss_adjust_factor * self._cur_lp_fee_loss_multiplier

  def calculate_current_fee(self, cur_price):
    if not self._has_lp_position:
      return
    
    if cur_price < self._cur_lp_price_upper and cur_price > self._cur_lp_price_lower:
      self._cur_lp_in_cnt += 1
      self._cur_lp_fee += self._klp_fee_per_unit * (self._cur_lp_enter_amount / 10000.0) * self._cur_lp_fee_loss_multiplier
    else:
      self._cur_lp_out_cnt += 1

  def calculate_current_accept_win_size(self, cur_price):
    if not self._has_lp_position:
      return None
    
    accept_loss = self._cur_lp_fee * 0.8
    cur_price_diff = abs(cur_price - self._cur_lp_enter_price)
    accept_price_diff = math.sqrt(accept_loss / self._cur_loss_per_unit)
    return accept_price_diff

  def adjust_accept_win_size(self, accept_win_size, bias, cur_price):
    if not self._has_lp_position:
      return accept_win_size

    bias_lower_bar = 1.2 ** 2
    bias_upper_bar = 2.0 ** 2
    bias_lower_factor = 1.0
    bias_upper_factor = 5.0

    if bias > - 1.2 ** 2 and bias < 0.8 ** 2:
      return accept_win_size
    
    if (cur_price - self._cur_lp_enter_price) * bias < 0.0:
      return accept_win_size

    bias_factor = ((math.sqrt(abs(bias)) - (0.7 if bias < 0.0 else 0.3)) * 2.0) ** 2
    accept_win_size *= bias_factor
    return accept_win_size


  def is_bias_real_withdraw(self, bias, cur_price):
    price_diff = cur_price - self._cur_lp_enter_price
    base_minus_bias_bar = 1.2 ** 2
    base_plus_bias_bar = 1.1 ** 2
    #base_minus_bias_bar = 1.2
    #base_plus_bias_bar = 1.1
    half_win_size = 30.0
    bias_bar_factor = 1.0
    if abs(price_diff) > 5.0 + 0.001:
      bias_bar_factor = (half_win_size - abs(price_diff)) / (half_win_size - 5.0)
      bias_bar_factor = max(0.0, bias_bar_factor)
    
    
    if bias < 0.0:
      #if bias < -0.25 and cur_price > self._cur_lp_enter_price:
      #  logging.info("high possibility to come back")
      #  return False
      bias_bar = base_minus_bias_bar * bias_bar_factor
      #bias_bar = (base_minus_bias_bar * bias_bar_factor) ** 2
      logging.info("real bias bar:%f", bias_bar)
      return bias < -bias_bar
    else:
      #if bias > 0.25 and cur_price < self._cur_lp_enter_price:
      #  logging.info("high possibility to come back")
      #  return False
      bias_bar = base_plus_bias_bar * bias_bar_factor
      #bias_bar = (base_plus_bias_bar * bias_bar_factor) ** 2
      logging.info("real bias bar:%f", bias_bar)
      return bias > bias_bar

  def process_new_tick_bias(self, uniswap_ts, uniswap_price, binance_price):
    if uniswap_ts == self.last_processed_uniswap_ts:
      return
    self.last_processed_uniswap_ts = uniswap_ts
    self._mid_provide_bias_calculator.insert_record(binance_price, uniswap_price)
    self._long_provide_bias_calculator.insert_record(binance_price, uniswap_price)
    self._normal_withdraw_bias_calculator.insert_record(binance_price, uniswap_price)
    self._danger_withdraw_bias_calculator.insert_record(binance_price, uniswap_price)
    self._mid_provide_bias_calculator.print_info(self._logger)
    self._long_provide_bias_calculator.print_info(self._logger)
    self._normal_withdraw_bias_calculator.print_info(self._logger)
    self._danger_withdraw_bias_calculator.print_info(self._logger)

  def on_book_process(self, product, book):
    if product.exchange == 'Uniswap':
      if abs(book.timestamp - self._last_binance_book_ts) <= self._kmax_diff_ms * 10 ** 6:
        self.process_new_tick_bias(book.timestamp, (book.ask0().price + book.bid0().price) / 2.0, (self._last_binance_ask0 + self._last_binance_bid0) / 2.0)

    elif product.exchange == 'Binance':
      if abs(book.timestamp - self._last_uniswap_book_ts) <= self._kmax_diff_ms * 10 ** 6:
        self.process_new_tick_bias(self._last_uniswap_book_ts, (self._last_uniswap_ask0 + self._last_uniswap_bid0) / 2.0, (book.ask0().price + book.bid0().price) / 2.0)

    if product != self._trade_product:
      return
    
    self._uniswap_tick_cnt += 1
    askp, bidp = book.ask0().price, book.bid0().price
    midp = (float)(askp + bidp) / 2.0
    self._logger.info('mid price:%f' % (midp))
    self._window_estimator.insert_record(midp) 
    self._mid_win_estimator.insert_record(midp)
    #self._window_estimator.print_info(self._logger)
    #self._mid_win_estimator.print_info(self._logger)

    if not self._window_estimator.has_enough_record() or not self._mid_win_estimator.has_enough_record() \
    or not self._mid_provide_bias_calculator.has_enough_record() or not self._long_provide_bias_calculator.has_enough_record() \
    or not self._normal_withdraw_bias_calculator.has_enough_record() or not self._danger_withdraw_bias_calculator.has_enough_record():
      return

    if book.timestamp - self._last_op_ts < self._kmin_op_interval:
      return

    if not self._has_lp_position:
      avg_price = self._window_estimator.get_avg_price()
      half_window_size = self._window_estimator.get_half_window_size() * self._kwindow_size_multiplier
      if avg_price < 1.0e-6 or half_window_size < 1.0e-6:
        return

      if not self._mid_provide_bias_calculator.should_provide() or not self._long_provide_bias_calculator.should_provide() \
      or not self._normal_withdraw_bias_calculator.should_provide():
        return

      half_window_size = max(half_window_size, 75.0)
      price_upper = midp + half_window_size
      price_lower = max(midp - half_window_size, 0.01)

      self._last_op_ts = book.timestamp
      self.update_lp_data_after_provide(midp, price_upper, price_lower, 10000.0)
    else:
      self.calculate_current_fee(midp)
      self.calculate_current_loss(midp)
      if self._uniswap_tick_cnt % 1800 == 0:
        self.print_lp_info()
      accept_price_diff = self.calculate_current_accept_win_size(self._mid_win_estimator.get_avg_price())
      self._logger.info('accept price diff:%f' % (accept_price_diff))
      accept_price_diff = max(5.0, accept_price_diff)
      accept_price_diff = self.adjust_accept_win_size(accept_price_diff, self._normal_withdraw_bias_calculator.get_bias_val(), midp)
      self._logger.info('adjusted accept price diff:%f' % (accept_price_diff))
      need_withdraw = self.is_bias_real_withdraw(self._normal_withdraw_bias_calculator.get_bias_val(), midp)
      if need_withdraw and abs(midp - self._cur_lp_enter_price) < accept_price_diff \
      or self._window_estimator.get_avg_price() < self._cur_lp_price_lower - 10.0 or self._window_estimator.get_avg_price() > self._cur_lp_price_upper + 10.0:
      #if self._normal_withdraw_bias_calculator.is_minus_bias_withdraw() and abs(midp - self._cur_lp_enter_price) < accept_price_diff \
      #or self._normal_withdraw_bias_calculator.is_plus_bias_withdraw() and abs(midp - self._cur_lp_enter_price) < accept_price_diff \
      #or self._window_estimator.get_avg_price() < self._cur_lp_price_lower - 10.0 or self._window_estimator.get_avg_price() > self._cur_lp_price_upper + 10.0:
      #or self._danger_withdraw_bias_calculator.is_minus_bias_withdraw() and midp < self._cur_lp_enter_price \
      #or self._danger_withdraw_bias_calculator.is_plus_bias_withdraw() and midp > self._cur_lp_enter_price \
      #or abs(midp - self._cur_lp_enter_price) > 200.0:
      #if self._withdraw_bias_calculator.is_minus_bias_withdraw() and midp < self._cur_lp_enter_price \
      #or self._withdraw_bias_calculator.is_plus_bias_withdraw() and midp > self._cur_lp_enter_price \
      #or self._window_estimator.get_avg_price() < self._cur_lp_price_lower or self._window_estimator.get_avg_price() > self._cur_lp_price_upper:
      #or abs(self._mid_win_estimator.get_avg_price() - self._window_estimator.get_avg_price()) > 3.0 and abs(midp - self._cur_lp_enter_price) < 5.0 \
      #or new_win_lower < self._cur_lp_price_lower or new_win_upper > self._cur_lp_price_upper:
      #or abs(midp - self._cur_lp_enter_price) > 100.0:
        self._last_op_ts = book.timestamp
        self.update_lp_data_after_withdraw(midp)

class SimpleAMM_M2SubStrategy(SingleProductSubStrategyAMMBase):
  def __init__(self, model_profile, logger, parent):
    super().__init__(model_profile, logger, parent)
    self._last_op_ts = 0
    self._kmin_op_interval = 20000000000
    
    # for on_book_process1
    '''
    self._window_estimator = WindowEstimator(max_record_cnt=3600, range_multiplier=2.0)
    self._mid_win_estimator = WindowEstimator(max_record_cnt=600, range_multiplier=2.0)
    self._kwindow_size_multiplier = 3.0
    self._withdraw_bias_calculator = BiasCalculator(max_record_cnt=60, withdraw_signal_bar=1.2 ** 2, provide_signal_bar=0.2 ** 2)
    self._provide_bias_calculator = BiasCalculator(max_record_cnt=300, withdraw_signal_bar=2.5 ** 2, provide_signal_bar=0.2 ** 2)
    '''

    self._short_provide_bias_calculator = BiasCalculator(max_record_cnt=60, withdraw_signal_bar=0.8 ** 2, provide_signal_bar=0.3 ** 2)
    self._mid_provide_bias_calculator = BiasCalculator(max_record_cnt=600, withdraw_signal_bar=2.5 ** 2, provide_signal_bar=0.3 ** 2)
    self._long_provide_bias_calculator = BiasCalculator(max_record_cnt=3600, withdraw_signal_bar=2.5 ** 2, provide_signal_bar=0.3 ** 2)

    self._window_estimator = WindowEstimator(max_record_cnt=3600, range_multiplier=2.0)
    self._mid_win_estimator = WindowEstimator(max_record_cnt=60, range_multiplier=2.0)
    self._recent_win_estimator = WindowEstimator(max_record_cnt=30, range_multiplier=2.0)
    self._mid_recent_win_estimator = WindowEstimator(max_record_cnt=60, range_multiplier=2.0)
    self._kwindow_size_multiplier = 3.0

    #self._klp_fee_per_unit = 0.000381944 / 2.0
    self._klp_fee_per_unit = 0.000381944
    self._klp_loss_adjust_factor = (0.26 / 0.001924) / 2.0
    self._has_lp_position = False
    self._cur_lp_enter_price = 0.0
    self._cur_lp_enter_amount = 0.0
    self._cur_lp_enter_base_amount = 0.0
    self._cur_lp_enter_quote_amount = 0.0
    self._cur_lp_price_lower = 0.0
    self._cur_lp_price_upper = 0.0
    self._cur_lp_fee_loss_multiplier = 0.0
    self._cur_lp_in_cnt = 0
    self._cur_lp_out_cnt = 0

    self._cur_loss_per_unit = 0.0
    self._cur_lp_loss = 0.0
    self._total_lp_loss = 0.0

    self._cur_lp_fee = 0.0
    self._total_fee = 0.0
    self._total_net_profit = 0.0
    self._last_lp_out_percentage = 0.0

    self.last_processed_uniswap_ts = 0

    self._uniswap_tick_cnt = 0

    self._end_timestamp = to_timestamp_int(datetime.strptime("20230528T235958", "%Y%m%dT%H%M%S"))

  def reset_lp_data(self):
    self._has_lp_position = False
    self._cur_lp_enter_price = 0.0
    self._cur_lp_enter_amount = 0.0
    self._cur_lp_enter_base_amount = 0.0
    self._cur_lp_enter_quote_amount = 0.0
    self._cur_lp_price_lower = 0.0
    self._cur_lp_price_upper = 0.0
    self._cur_lp_fee_loss_multiplier = 0.0
    self._cur_lp_in_cnt = 0
    self._cur_lp_out_cnt = 0
    self._cur_lp_loss = 0.0
    self._cur_lp_fee = 0.0
    self._cur_loss_per_unit = 0.0

  def print_lp_info(self):
    if not self._has_lp_position:
      return
    
    self._logger.info('current liquidity: enter price:%f, amount: %f, price lower:%f, price upper:%f, record in:%d, record out:%f' % (
            self._cur_lp_enter_price, self._cur_lp_enter_amount, self._cur_lp_price_lower, self._cur_lp_price_upper, self._cur_lp_in_cnt, self._cur_lp_out_cnt))
    self._logger.info('current status: current loss:%f, current fee: %f, total loss:%f, total fee:%f, total net profit:%f' % (
      self._cur_lp_loss, self._cur_lp_fee, self._total_lp_loss, self._total_fee, self._total_net_profit))

  def update_lp_data_after_withdraw(self, cur_price):
    if not self._has_lp_position:
      return
    
    self._total_lp_loss += self._cur_lp_loss
    self._total_fee += self._cur_lp_fee
    self._total_net_profit = self._total_fee - self._total_lp_loss
    if self._cur_lp_out_cnt + self._cur_lp_in_cnt == 0:
      self._last_lp_out_percentage = -1
    else:
      self._last_lp_out_percentage = (float)(self._cur_lp_out_cnt) / (float)(self._cur_lp_out_cnt + self._cur_lp_in_cnt)
    self._logger.info('withdraw liquidity: enter price:%f, quit price: %f, amount: %f, price lower:%f, price upper:%f, record span:%d, record out percentage:%f' % (
            self._cur_lp_enter_price, cur_price, self._cur_lp_enter_amount, self._cur_lp_price_lower, self._cur_lp_price_upper, self._cur_lp_in_cnt + self._cur_lp_out_cnt, self._last_lp_out_percentage))
    self._logger.info('current status: current loss:%f, current fee: %f, total loss:%f, total fee:%f, total net profit:%f' % (
      self._cur_lp_loss, self._cur_lp_fee, self._total_lp_loss, self._total_fee, self._total_net_profit))
    self.reset_lp_data()

  def update_lp_data_after_provide(self, cur_price, price_upper, price_lower, amount):
    if self._has_lp_position:
      return

    self._has_lp_position = True
    self._cur_lp_enter_price = cur_price
    self._cur_lp_enter_amount = amount
    self._cur_lp_enter_base_amount = amount / (cur_price * 2.0) 
    self._cur_lp_enter_quote_amount = amount / 2.0
    self._cur_lp_price_lower = price_lower
    self._cur_lp_price_upper = price_upper
    self._cur_lp_fee_loss_multiplier = 100.0 / (price_upper - price_lower)
    self._cur_lp_in_cnt = 0
    self._cur_lp_out_cnt = 0
    self._cur_lp_loss = 0.0
    self._cur_lp_fee = 0.0
    self.calculate_current_loss(cur_price + 1.0)
    self._cur_loss_per_unit = self._cur_lp_loss
    self._logger.info('provide liquidity: enter price:%f, amount: %f, price lower:%f, price upper:%f, loss per unit:%f' % (
      self._cur_lp_enter_price, self._cur_lp_enter_amount, self._cur_lp_price_lower, self._cur_lp_price_upper, self._cur_loss_per_unit))

  def calculate_current_loss(self, cur_price):
    if not self._has_lp_position:
      return
    
    lp_quit_base_amount = self._cur_lp_enter_base_amount * math.sqrt(self._cur_lp_enter_price / cur_price)
    lp_quit_quote_amount = self._cur_lp_enter_quote_amount * math.sqrt(cur_price / self._cur_lp_enter_price)
    raw_loss = (self._cur_lp_enter_base_amount * cur_price + self._cur_lp_enter_quote_amount) - (lp_quit_base_amount * cur_price + lp_quit_quote_amount)
    self._cur_lp_loss = raw_loss * self._klp_loss_adjust_factor * self._cur_lp_fee_loss_multiplier

  def calculate_current_fee(self, cur_price):
    if not self._has_lp_position:
      return
    
    if cur_price < self._cur_lp_price_upper and cur_price > self._cur_lp_price_lower:
      self._cur_lp_in_cnt += 1
      self._cur_lp_fee += self._klp_fee_per_unit * (self._cur_lp_enter_amount / 10000.0) * self._cur_lp_fee_loss_multiplier
    else:
      self._cur_lp_out_cnt += 1

  def process_new_tick_bias(self, uniswap_ts, uniswap_price, binance_price):
    if uniswap_ts == self.last_processed_uniswap_ts:
      return
    self.last_processed_uniswap_ts = uniswap_ts
    self._short_provide_bias_calculator.insert_record(binance_price, uniswap_price)
    self._mid_provide_bias_calculator.insert_record(binance_price, uniswap_price)
    self._long_provide_bias_calculator.insert_record(binance_price, uniswap_price)
    self._short_provide_bias_calculator.print_info(self._logger)
    self._mid_provide_bias_calculator.print_info(self._logger)
    self._long_provide_bias_calculator.print_info(self._logger)

  def calculate_price_diff_allowed(self, gradient):
    gradient = abs(gradient)
    if gradient >= 0.5:
      return 5.0
    
    price_diff_allowed = 5.0 * (2 ** (5.0 - gradient * 10.0))
    return price_diff_allowed

  def calculate_price_diff_allowed2(self, long_avg_cur, long_avg_last, short_avg_cur, short_avg_last, enter_price):
    gradient = abs(long_avg_cur - long_avg_last)
    price_diff_allowed = 5.0
    if gradient < 0.5:
      price_diff_allowed = 5.0 * (1.7 ** (5.0 - gradient * 10.0))

    long_avg_diff = long_avg_cur - long_avg_last
    short_avg_diff = (short_avg_cur - short_avg_last) / 5.0
    short_avg_diff = min(1.0, max(-1.0, short_avg_diff))
    if long_avg_diff > 0.0 and short_avg_diff < long_avg_diff or long_avg_diff < 0.0 and short_avg_diff > long_avg_diff:
      away_factor = 1.0 + abs(long_avg_diff - short_avg_diff) * 4.0
      price_diff_allowed *= away_factor

    if (short_avg_cur - short_avg_last) * (enter_price - short_avg_cur) > 0.0:
      price_diff_allowed *= 2
    #if (long_avg_cur - long_avg_last) * (short_avg_cur - short_avg_last) < 0.0:
    #  price_diff_allowed *= 2
    #elif (short_avg_cur - short_avg_last) * (enter_price - short_avg_cur) > 0.0:
    #  price_diff_allowed *= 2

    return price_diff_allowed


  def on_book_process(self, product, book):
    
    if product.exchange == 'Uniswap':
      if abs(book.timestamp - self._last_binance_book_ts) <= self._kmax_diff_ms * 10 ** 6:
        self.process_new_tick_bias(book.timestamp, (book.ask0().price + book.bid0().price) / 2.0, (self._last_binance_ask0 + self._last_binance_bid0) / 2.0)

    elif product.exchange == 'Binance':
      if abs(book.timestamp - self._last_uniswap_book_ts) <= self._kmax_diff_ms * 10 ** 6:
        self.process_new_tick_bias(self._last_uniswap_book_ts, (self._last_uniswap_ask0 + self._last_uniswap_bid0) / 2.0, (book.ask0().price + book.bid0().price) / 2.0)
    
    if product != self._trade_product:
      return
    
    self._uniswap_tick_cnt += 1
    askp, bidp = book.ask0().price, book.bid0().price
    midp = (float)(askp + bidp) / 2.0
    self._logger.info('mid price:%f' % (midp))
    self._window_estimator.insert_record(midp)
    self._recent_win_estimator.insert_record(midp)
    #self._window_estimator.print_info(self._logger)
    #self._recent_win_estimator.print_info(self._logger)

    if not self._window_estimator.has_enough_record():
      return

    avg_price_current = self._window_estimator.get_avg_price()
    recent_price_current = self._recent_win_estimator.get_avg_price()
    self._mid_win_estimator.insert_record(avg_price_current)
    self._mid_recent_win_estimator.insert_record(recent_price_current)
    #self._mid_win_estimator.print_info(self._logger)
    #self._mid_recent_win_estimator.print_info(self._logger)
    if not self._mid_win_estimator.has_enough_record() or not self._mid_recent_win_estimator.has_enough_record():
      return

    avg_price_last_min = self._mid_win_estimator._record_queue[0]
    avg_price_diff = avg_price_current - avg_price_last_min
    #price_diff_allowed = self.calculate_price_diff_allowed(avg_price_diff)
    recent_price_last_min = self._mid_recent_win_estimator._record_queue[0]
    recent_price_diff = recent_price_current - recent_price_last_min
    self._logger.info('avg_price_last_min:%f, avg_price_current: %f, avg_price_diff:%f' % (
      avg_price_last_min, avg_price_current, avg_price_diff))
    self._logger.info('recent_price_last_min:%f, recent_price_current: %f, recent_price_diff:%f' % (
      recent_price_last_min, recent_price_current, recent_price_diff))

    if book.timestamp - self._last_op_ts < self._kmin_op_interval:
      return

    if not self._has_lp_position:
      if not self._long_provide_bias_calculator.has_enough_record():
        return
      
      if self._long_provide_bias_calculator.should_provide() and self._mid_provide_bias_calculator.should_provide() and self._short_provide_bias_calculator.should_provide():
        half_window_size = 75.0
        price_upper = midp + half_window_size
        price_lower = max(midp - half_window_size, 0.01)

        self._last_op_ts = book.timestamp
        self.update_lp_data_after_provide(midp, price_upper, price_lower, 10000.0)
    else:
      self.calculate_current_fee(midp)
      self.calculate_current_loss(midp)
      if self._uniswap_tick_cnt % 1800 == 0:
        self.print_lp_info()
      price_diff_allowed = self.calculate_price_diff_allowed2(avg_price_current, avg_price_last_min, recent_price_current, recent_price_last_min, self._cur_lp_enter_price)
      self._logger.info('price_diff_allowed:%f' % (price_diff_allowed))
      if abs(self._recent_win_estimator.get_avg_price() - self._cur_lp_enter_price) > price_diff_allowed:
      #or abs(midp - self._cur_lp_enter_price) > 100.0:
        self._last_op_ts = book.timestamp
        self.update_lp_data_after_withdraw(midp)

class SimpleAMMSubStrategy(SingleProductSubStrategyAMMBase):
  def __init__(self, model_profile, logger, parent):
    super().__init__(model_profile, logger, parent) 
    self._kprice_diff_bar = 0.4
    self._is_current_op_provide = True
    self._last_op_ts = 0
    self._kmin_op_interval = 15000000000
    
    self._window_estimator = WindowEstimator(max_record_cnt=600, range_multiplier=4.0)
    self._mid_win_estimator = WindowEstimator(max_record_cnt=60, range_multiplier=4.0)
    self._kwindow_size_multiplier = 1.5
    self._withdraw_bias_calculator = BiasCalculator(max_record_cnt=60, withdraw_signal_bar=3.0 ** 2, provide_signal_bar=0.2 ** 2)
    #self._provide_bias_calculator = BiasCalculator(max_record_cnt=300, withdraw_signal_bar=3.0 ** 2, provide_signal_bar=0.2 ** 2)
    #self._bias_validator = BiasValidator(bias_group_ts_diff = 600 * 10 ** 9, bias_validate_ts_diff = 3600 * 10 ** 9, bias_bar = 4.0, price_diff_bar = 5.0)
    self._provide_bias_calculator = BiasCalculator(max_record_cnt=FLAGS.bias_record_cnt, withdraw_signal_bar=3.0 ** 2, provide_signal_bar=0.2 ** 2)
    #self._bias_validator = BiasValidator(bias_group_ts_diff = 600 * 10 ** 9, bias_validate_ts_diff = FLAGS.bias_validate_ts_diff, bias_bar = FLAGS.bias_bar, price_diff_bar = FLAGS.price_diff_bar)
    #self._bias_validator = BiasValidator2(bias_group_ts_diff = 600 * 10 ** 9, bias_validate_ts_diff = FLAGS.bias_validate_ts_diff, bias_bar = FLAGS.bias_bar, price_diff_bar = FLAGS.price_diff_bar)
    self._bias_validator = BiasValidator3(bias_group_ts_diff = 600 * 10 ** 9, bias_validate_ts_diff = FLAGS.bias_validate_ts_diff, bias_bar = FLAGS.bias_bar, leave_bias_bar=FLAGS.leave_bias_bar, price_diff_bar = FLAGS.price_diff_bar)

    self._klp_fee_per_unit = 0.000381944
    self._klp_loss_adjust_factor = 0.26 / 0.001924
    self._has_lp_position = False
    self._cur_lp_enter_price = 0.0
    self._cur_lp_enter_amount = 0.0
    self._cur_lp_enter_base_amount = 0.0
    self._cur_lp_enter_quote_amount = 0.0
    self._cur_lp_price_lower = 0.0
    self._cur_lp_price_upper = 0.0
    self._cur_lp_fee_loss_multiplier = 0.0
    self._cur_lp_in_cnt = 0
    self._cur_lp_out_cnt = 0

    self._cur_lp_loss = 0.0
    self._total_lp_loss = 0.0

    self._cur_lp_fee = 0.0
    self._total_fee = 0.0
    self._total_net_profit = 0.0
    self._last_lp_out_percentage = 0.0

    self.last_processed_uniswap_ts = 0

    self._uniswap_tick_cnt = 0

    self._end_timestamp = to_timestamp_int(datetime.strptime("20230521T235959", "%Y%m%dT%H%M%S"))

  def reset_lp_data(self):
    self._has_lp_position = False
    self._cur_lp_enter_price = 0.0
    self._cur_lp_enter_amount = 0.0
    self._cur_lp_enter_base_amount = 0.0
    self._cur_lp_enter_quote_amount = 0.0
    self._cur_lp_price_lower = 0.0
    self._cur_lp_price_upper = 0.0
    self._cur_lp_fee_loss_multiplier = 0.0
    self._cur_lp_in_cnt = 0
    self._cur_lp_out_cnt = 0
    self._cur_lp_loss = 0.0
    self._cur_lp_fee = 0.0

  def print_lp_info(self):
    if not self._has_lp_position:
      return
    
    self._logger.info('current liquidity: enter price:%f, amount: %f, price lower:%f, price upper:%f, record in:%d, record out:%f' % (
            self._cur_lp_enter_price, self._cur_lp_enter_amount, self._cur_lp_price_lower, self._cur_lp_price_upper, self._cur_lp_in_cnt, self._cur_lp_out_cnt))
    self._logger.info('current status: current loss:%f, current fee: %f, total loss:%f, total fee:%f, total net profit:%f' % (
      self._cur_lp_loss, self._cur_lp_fee, self._total_lp_loss, self._total_fee, self._total_net_profit))

  def update_lp_data_after_withdraw(self, cur_price):
    if not self._has_lp_position:
      return
    
    self._total_lp_loss += self._cur_lp_loss
    self._total_fee += self._cur_lp_fee
    self._total_net_profit = self._total_fee - self._total_lp_loss
    if self._cur_lp_out_cnt + self._cur_lp_in_cnt == 0:
      self._last_lp_out_percentage = -1
    else:
      self._last_lp_out_percentage = (float)(self._cur_lp_out_cnt) / (float)(self._cur_lp_out_cnt + self._cur_lp_in_cnt)
    self._logger.info('withdraw liquidity: enter price:%f, quit price: %f, amount: %f, price lower:%f, price upper:%f, record span:%d, record out percentage:%f' % (
            self._cur_lp_enter_price, cur_price, self._cur_lp_enter_amount, self._cur_lp_price_lower, self._cur_lp_price_upper, self._cur_lp_in_cnt + self._cur_lp_out_cnt, self._last_lp_out_percentage))
    self._logger.info('current status: current loss:%f, current fee: %f, total loss:%f, total fee:%f, total net profit:%f' % (
      self._cur_lp_loss, self._cur_lp_fee, self._total_lp_loss, self._total_fee, self._total_net_profit))
    self.reset_lp_data()

  def update_lp_data_after_provide(self, cur_price, price_upper, price_lower, amount):
    if self._has_lp_position:
      return

    self._has_lp_position = True
    self._cur_lp_enter_price = cur_price
    self._cur_lp_enter_amount = amount
    self._cur_lp_enter_base_amount = amount / (cur_price * 2.0) 
    self._cur_lp_enter_quote_amount = amount / 2.0
    self._cur_lp_price_lower = price_lower
    self._cur_lp_price_upper = price_upper
    self._cur_lp_fee_loss_multiplier = 100.0 / (price_upper - price_lower)
    self._cur_lp_in_cnt = 0
    self._cur_lp_out_cnt = 0
    self._cur_lp_loss = 0.0
    self._cur_lp_fee = 0.0
    self._logger.info('provide liquidity: enter price:%f, amount: %f, price lower:%f, price upper:%f,' % (
      self._cur_lp_enter_price, self._cur_lp_enter_amount, self._cur_lp_price_lower, self._cur_lp_price_upper))

  def calculate_current_loss(self, cur_price):
    if not self._has_lp_position:
      return
    
    lp_quit_base_amount = self._cur_lp_enter_base_amount * math.sqrt(self._cur_lp_enter_price / cur_price)
    lp_quit_quote_amount = self._cur_lp_enter_quote_amount * math.sqrt(cur_price / self._cur_lp_enter_price)
    raw_loss = (self._cur_lp_enter_base_amount * cur_price + self._cur_lp_enter_quote_amount) - (lp_quit_base_amount * cur_price + lp_quit_quote_amount)
    self._cur_lp_loss = raw_loss * self._klp_loss_adjust_factor * self._cur_lp_fee_loss_multiplier

  def calculate_current_fee(self, cur_price):
    if not self._has_lp_position:
      return
    
    if cur_price < self._cur_lp_price_upper and cur_price > self._cur_lp_price_lower:
      self._cur_lp_in_cnt += 1
      self._cur_lp_fee += self._klp_fee_per_unit * self._cur_lp_fee_loss_multiplier
    else:
      self._cur_lp_out_cnt += 1

  def process_new_tick_bias(self, uniswap_ts, uniswap_price, binance_price):
    if uniswap_ts == self.last_processed_uniswap_ts:
      return
    self.last_processed_uniswap_ts = uniswap_ts
    self._provide_bias_calculator.insert_record(binance_price, uniswap_price)
    self._withdraw_bias_calculator.insert_record(binance_price, uniswap_price)
    self._bias_validator.insert_bias(uniswap_ts, self._provide_bias_calculator.get_bias_val(), uniswap_price)
    self._provide_bias_calculator.print_info(self._logger)
    self._withdraw_bias_calculator.print_info(self._logger)
    if uniswap_ts > self._end_timestamp:
      logging.info("bias validation result:%f, valid cnt:%d, all cnt:%d", self._bias_validator.get_valid_ratio(), self._bias_validator.get_valid_cnt(), self._bias_validator.get_all_cnt())

  def on_book_process(self, product, book):
    if product.exchange == 'Uniswap':
      if abs(book.timestamp - self._last_binance_book_ts) <= self._kmax_diff_ms * 10 ** 6:
        self.process_new_tick_bias(book.timestamp, (book.ask0().price + book.bid0().price) / 2.0, (self._last_binance_ask0 + self._last_binance_bid0) / 2.0)

    elif product.exchange == 'Binance':
      if abs(book.timestamp - self._last_uniswap_book_ts) <= self._kmax_diff_ms * 10 ** 6:
        self.process_new_tick_bias(self._last_uniswap_book_ts, (self._last_uniswap_ask0 + self._last_uniswap_bid0) / 2.0, (book.ask0().price + book.bid0().price) / 2.0)

    if product != self._trade_product:
      return
    
    self._uniswap_tick_cnt += 1
    askp, bidp = book.ask0().price, book.bid0().price
    midp = (float)(askp + bidp) / 2.0
    self._logger.info('mid price:%f' % (midp))
    #self._window_estimator.insert_record(midp) 
    #self._mid_win_estimator.insert_record(midp)
    #self._window_estimator.print_info(self._logger)
    #self._mid_win_estimator.print_info(self._logger)
    
    #if not self._window_estimator.has_enough_record() or not self._provide_bias_calculator.has_enough_record() or not self._withdraw_bias_calculator.has_enough_record():
    #  return
    if not self._provide_bias_calculator.has_enough_record() or not self._withdraw_bias_calculator.has_enough_record():
      return

  def on_book_process5(self, product, book):
    if product.exchange == 'Uniswap':
      if abs(book.timestamp - self._last_binance_book_ts) <= self._kmax_diff_ms * 10 ** 6:
        self.process_new_tick_bias(book.timestamp, (book.ask0().price + book.bid0().price) / 2.0, (self._last_binance_ask0 + self._last_binance_bid0) / 2.0)

    elif product.exchange == 'Binance':
      if abs(book.timestamp - self._last_uniswap_book_ts) <= self._kmax_diff_ms * 10 ** 6:
        self.process_new_tick_bias(self._last_uniswap_book_ts, (self._last_uniswap_ask0 + self._last_uniswap_bid0) / 2.0, (book.ask0().price + book.bid0().price) / 2.0)

    if product != self._trade_product:
      return
    
    self._uniswap_tick_cnt += 1
    askp, bidp = book.ask0().price, book.bid0().price
    midp = (float)(askp + bidp) / 2.0
    self._logger.info('mid price:%f' % (midp))
    self._window_estimator.insert_record(midp) 
    self._mid_win_estimator.insert_record(midp)
    #self._window_estimator.print_info(self._logger)
    #self._mid_win_estimator.print_info(self._logger)
    if not self._window_estimator.has_enough_record() or not self._provide_bias_calculator.has_enough_record() or not self._withdraw_bias_calculator.has_enough_record():
      return
    
    if not self._has_lp_position:
      avg_price = self._window_estimator.get_avg_price()
      half_window_size = self._window_estimator.get_half_window_size()
      if avg_price < 1.0e-6 or half_window_size < 1.0e-6:
        return
      if not self._provide_bias_calculator.should_provide() or not self._withdraw_bias_calculator.should_provide():
        return
      #if half_window_size > 10.0 or self._mid_win_estimator.get_half_window_size() > 5.0 \
      #or abs(midp - self._window_estimator.get_avg_price()) > 2.0 or abs(midp - self._mid_win_estimator.get_avg_price()) > 1.0:
      #  return
      half_window_size = max(half_window_size, 0.1)
      #price_upper = midp + half_window_size
      #price_lower = max(midp - half_window_size, 0.01)
      price_upper = midp + 50.0
      price_lower = midp - 50.0
      self.update_lp_data_after_provide(midp, price_upper, price_lower, 10000.0)
    else:
      self.calculate_current_fee(midp)
      self.calculate_current_loss(midp)
      if self._uniswap_tick_cnt % 1800 == 0:
        self.print_lp_info()
      window_lower, window_upper = self._window_estimator.get_price_window()
      if window_lower < 1.0e-6 or window_upper < 1.0e-6 or window_upper < window_lower:
        return
      new_win_lower = window_lower + (window_upper - window_lower) / 4.0
      new_win_upper = window_upper - (window_upper - window_lower) / 4.0
      window_upper = new_win_upper
      window_lower = new_win_lower
      if self._withdraw_bias_calculator.is_minus_bias_withdraw() and midp < self._cur_lp_enter_price \
      or self._withdraw_bias_calculator.is_plus_bias_withdraw() and midp > self._cur_lp_enter_price \
      or abs(midp - self._cur_lp_enter_price) > 100.0:
      #or midp - self._window_estimator.get_avg_price() > 4.0 and midp - self._cur_lp_enter_price > 4.0 \
      #or self._window_estimator.get_avg_price() - midp > 4.0 and self._cur_lp_enter_price - midp > 4.0 \
      #or midp - self._mid_win_estimator.get_avg_price() > 2.0 and midp - self._cur_lp_enter_price > 2.0 \
      #or self._mid_win_estimator.get_avg_price() - midp > 2.0 and self._cur_lp_enter_price - midp > 2.0:
        self.update_lp_data_after_withdraw(midp)


  def on_book_process4(self, product, book):
    if product != self._trade_product:
      return
    
    self._uniswap_tick_cnt += 1
    askp, bidp = book.ask0().price, book.bid0().price
    midp = (float)(askp + bidp) / 2.0
    self._logger.info('mid price:%f' % (midp))
    self._window_estimator.insert_record(midp)
    self._mid_win_estimator.insert_record(midp)
    self._window_estimator.print_info(self._logger)
    self._mid_win_estimator.print_info(self._logger)
    if not self._window_estimator.has_enough_record():
      return
    
    if not self._has_lp_position:
      avg_price = self._window_estimator.get_avg_price()
      half_window_size = self._window_estimator.get_half_window_size()
      if avg_price < 1.0e-6 or half_window_size < 1.0e-6:
        return
      if half_window_size > 10.0 or self._mid_win_estimator.get_half_window_size() > 5.0 \
      or abs(midp - self._window_estimator.get_avg_price()) > 2.0 or abs(midp - self._mid_win_estimator.get_avg_price()) > 1.0:
        return
      half_window_size = max(half_window_size, 0.1)
      #price_upper = midp + half_window_size
      #price_lower = max(midp - half_window_size, 0.01)
      price_upper = midp + 30.0
      price_lower = midp - 30.0
      self.update_lp_data_after_provide(midp, price_upper, price_lower, 10000.0)
    else:
      self.calculate_current_fee(midp)
      self.calculate_current_loss(midp)
      if self._uniswap_tick_cnt % 1800 == 0:
        self.print_lp_info()
      window_lower, window_upper = self._window_estimator.get_price_window()
      if window_lower < 1.0e-6 or window_upper < 1.0e-6 or window_upper < window_lower:
        return
      new_win_lower = window_lower + (window_upper - window_lower) / 4.0
      new_win_upper = window_upper - (window_upper - window_lower) / 4.0
      window_upper = new_win_upper
      window_lower = new_win_lower
      if abs(midp - self._cur_lp_enter_price) > 20.0 \
      or midp - self._window_estimator.get_avg_price() > 4.0 and midp - self._cur_lp_enter_price > 4.0 \
      or self._window_estimator.get_avg_price() - midp > 4.0 and self._cur_lp_enter_price - midp > 4.0 \
      or midp - self._mid_win_estimator.get_avg_price() > 2.0 and midp - self._cur_lp_enter_price > 2.0 \
      or self._mid_win_estimator.get_avg_price() - midp > 2.0 and self._cur_lp_enter_price - midp > 2.0:
        self.update_lp_data_after_withdraw(midp)

  def on_book_process3(self, product, book):
    if product != self._trade_product:
      return
    
    self._uniswap_tick_cnt += 1
    askp, bidp = book.ask0().price, book.bid0().price
    midp = (float)(askp + bidp) / 2.0
    self._logger.info('mid price:%f' % (midp))
    self._window_estimator.insert_record(midp)
    self._mid_win_estimator.insert_record(midp)
    self._window_estimator.print_info(self._logger)
    self._mid_win_estimator.print_info(self._logger)
    if not self._window_estimator.has_enough_record():
      return
    
    if not self._has_lp_position:
      avg_price = self._window_estimator.get_avg_price()
      half_window_size = self._window_estimator.get_half_window_size()
      if avg_price < 1.0e-6 or half_window_size < 1.0e-6:
        return
      if half_window_size > 5.0 or self._mid_win_estimator.get_half_window_size() > 10.0 \
      or abs(midp - self._window_estimator.get_avg_price()) > 2.0 or abs(midp - self._mid_win_estimator.get_avg_price()) > 2.0:
        return
      half_window_size = max(half_window_size, 0.1)
      price_upper = midp + half_window_size
      price_lower = max(midp - half_window_size, 0.01)
      self.update_lp_data_after_provide(midp, price_upper, price_lower, 10000.0)
    else:
      self.calculate_current_fee(midp)
      self.calculate_current_loss(midp)
      if self._uniswap_tick_cnt % 1800 == 0:
        self.print_lp_info()
      window_lower, window_upper = self._window_estimator.get_price_window()
      if window_lower < 1.0e-6 or window_upper < 1.0e-6 or window_upper < window_lower:
        return
      new_win_lower = window_lower + (window_upper - window_lower) / 4.0
      new_win_upper = window_upper - (window_upper - window_lower) / 4.0
      window_upper = new_win_upper
      window_lower = new_win_lower
      if self._window_estimator.get_half_window_size() > 7.0 or self._mid_win_estimator.get_half_window_size() > 10.0 \
      or abs(midp - self._cur_lp_enter_price) > 5.0 \
      or abs(midp - self._window_estimator.get_avg_price()) > 3.0 and abs(midp - self._cur_lp_enter_price) > 3.0 \
      or abs(midp - self._mid_win_estimator.get_avg_price()) > 3.0 and abs(midp - self._cur_lp_enter_price) > 3.0:
        self.update_lp_data_after_withdraw(midp)

  def on_book_process1(self, product, book):
    if product != self._trade_product:
      return
    
    self._uniswap_tick_cnt += 1
    askp, bidp = book.ask0().price, book.bid0().price
    midp = (float)(askp + bidp) / 2.0
    self._logger.info('mid price:%f' % (midp))
    self._window_estimator.insert_record(midp)
    self._mid_win_estimator.insert_record(midp)
    self._window_estimator.print_info(self._logger)
    self._mid_win_estimator.print_info(self._logger)
    if not self._window_estimator.has_enough_record():
      return
    
    if not self._has_lp_position:
      avg_price = self._window_estimator.get_avg_price()
      half_window_size = self._window_estimator.get_half_window_size()
      if avg_price < 1.0e-6 or half_window_size < 1.0e-6:
        return
      if half_window_size > 5.0 or self._mid_win_estimator.get_half_window_size() > 10.0:
        return
      half_window_size = max(half_window_size, 0.1) * self._kwindow_size_multiplier
      price_upper = avg_price + half_window_size
      price_lower = max(avg_price - half_window_size, 0.01)
      self.update_lp_data_after_provide(midp, price_upper, price_lower, 10000.0)
    else:
      self.calculate_current_fee(midp)
      self.calculate_current_loss(midp)
      if self._uniswap_tick_cnt % 1800 == 0:
        self.print_lp_info()
      window_lower, window_upper = self._window_estimator.get_price_window()
      if window_lower < 1.0e-6 or window_upper < 1.0e-6 or window_upper < window_lower:
        return
      new_win_lower = window_lower + (window_upper - window_lower) / 4.0
      new_win_upper = window_upper - (window_upper - window_lower) / 4.0
      window_upper = new_win_upper
      window_lower = new_win_lower
      if self._window_estimator.get_half_window_size() > 7.0 or self._mid_win_estimator.get_half_window_size() > 10.0 \
      or new_win_lower < self._cur_lp_price_lower or new_win_upper > self._cur_lp_price_upper:
        self.update_lp_data_after_withdraw(midp)    

  def on_book_process2(self, product, book):
    if product != self._trade_product:
      return
    
    self._uniswap_tick_cnt += 1
    askp, bidp = book.ask0().price, book.bid0().price
    midp = (float)(askp + bidp) / 2.0
    self._logger.info('mid price:%f' % (midp))
    self._window_estimator.insert_record(midp)
    self._mid_win_estimator.insert_record(midp)
    self._window_estimator.print_info(self._logger)
    self._mid_win_estimator.print_info(self._logger)
    if not self._window_estimator.has_enough_record():
      return
    
    if not self._has_lp_position:
      avg_price = self._window_estimator.get_avg_price()
      half_window_size = self._window_estimator.get_half_window_size()
      if avg_price < 1.0e-6 or half_window_size < 1.0e-6:
        return
      if half_window_size > 5.0 and self._mid_win_estimator.get_half_window_size() > 2.5 or self._mid_win_estimator.get_half_window_size() > 10.0:
        return
      half_window_size = max(half_window_size, 0.1) * self._kwindow_size_multiplier
      price_upper = avg_price + half_window_size
      price_lower = max(avg_price - half_window_size, 0.01)
      self.update_lp_data_after_provide(midp, price_upper, price_lower, 10000.0)
    else:
      self.calculate_current_fee(midp)
      self.calculate_current_loss(midp)
      if self._uniswap_tick_cnt % 1800 == 0:
        self.print_lp_info()
      window_lower, window_upper = self._window_estimator.get_price_window()
      if window_lower < 1.0e-6 or window_upper < 1.0e-6 or window_upper < window_lower:
        return
      new_win_lower = window_lower + (window_upper - window_lower) / 4.0
      new_win_upper = window_upper - (window_upper - window_lower) / 4.0
      window_upper = new_win_upper
      window_lower = new_win_lower
      if self._window_estimator.get_half_window_size() > 7.0 and self._mid_win_estimator.get_half_window_size() > 3.5 or self._mid_win_estimator.get_half_window_size() > 10.0 \
      or new_win_lower < self._cur_lp_price_lower or new_win_upper > self._cur_lp_price_upper:
        self.update_lp_data_after_withdraw(midp)

class SimpleArbStrategy(UnaryMultifeedStrategyBase):
  def __init__(self, strat_config, logger=None):
    self._logger = logger or logging.getLogger('SimpleArbStrategy')
    assert isinstance(strat_config, PassUnhedgeStrategyConfig)
    self._config = strat_config
    self._set_traits(self._config.trade_subreq)
    super().__init__(products=self._config.get_products(),
                     currencies=self._config.get_currencies(),
                     logger=self._logger)

    self._book = BookMap(self._products, logger=self._logger)

    self._reserve_map = self._gen_reserve_map()
    printer_cls = get_pnl_balance_printer_cls(self._config.trade_subreq)
    self._printer = printer_cls(self._products, self._book, self._reserve_map, logger=self._logger)

    self._fill_dumper = FillLogDumper(self._config.trade_subreq)

    self._order_canceled_after_og_ready = True
    self._exit_attempt = 0
    self._exit_executor = None

    self._single_product_sub_strategies = []
    for profile in self._config.model_profile.values():
      self._single_product_sub_strategies.append(
          SimpleAMMV3SubStrategy(model_profile=profile,
                                   logger=self._logger,
                                   parent=self))

  @property
  def config(self):
    return self._config

  def _gen_reserve_map(self):
    reserve_map = {}
    if self._config.trade_subreq.market_type == 'Spot':
      for profile in self._config.model_profile.values():
        currency = profile.trade_product.base.currency
        min_pos = profile.config_dict['min_pos']
        max_pos = profile.config_dict['max_pos']
        reserve = 0.5 * (min_pos + max_pos)
        reserve_map[currency] = reserve
    return reserve_map

  def prepare(self):
    product_list = []
    for profile in self.config.model_profile.values():
      product = profile.trade_product
      product_list.append(product)
      product = profile.ref_product
      product_list.extend(product)
    define_fastfeed_flags()
    trading_date_start = self._config.config['sim']["trading_date_start"]
    trading_date_end = self._config.config['sim']["trading_date_end"]
    start_date = datetime.strptime(trading_date_start, "%Y%m%d")
    end_date = datetime.strptime(trading_date_end, "%Y%m%d")
    delta = timedelta(days=1)
    while start_date <= end_date:
        run_from_archive_or_cache(
          start_date.strftime("%Y%m%d"), FLAGS.feed_machine, product_list, self, True)
        start_date += delta
    

  def on_book_reset(self, products, book_builder_name, book_builder, feed_cache_key=None):
    for product in products:
        book_builder.subscribe_book(product, functools.partial(self.on_book, product))
        book_builder.subscribe_trade(product, functools.partial(self.on_trade, product))

  def on_book(self, product, book):
    for strat in self._single_product_sub_strategies:
      strat.on_book(product, book)

  def on_trade(self, product, trade):
    for strat in self._single_product_sub_strategies:
      strat.on_trade(product, trade)

  def signal_handler(self, sig, frame):
    self._logger.warning('Caught signal: %s', sig)
    self._ioloop.add_callback_from_signal(self.on_exit)

  def register_exit(self, timedelta=None):
    if timedelta is None:
      self._ioloop.add_callback(self.on_exit)
    else:
      self._ioloop.add_timeout(timedelta, self.on_exit)

  def on_exit(self):
    for strat in self._single_product_sub_strategies:
      strat.set_exit()

    self._exit_attempt += 1
    if self._exit_attempt > 5:
      self._ioloop.add_callback(self._ioloop.stop)

    finished = True
    if self._exit_executor is not None:
      ogs = to_list(self.order_gateway)
      self._logger.info('Exit callback executed.')
      finished = self._exit_executor.cancel_all_on_exit(ogs)

    if finished:
      self._ioloop.add_callback(self._ioloop.stop)
    else:
      timeout = datetime.timedelta(seconds=2)
      self._ioloop.add_timeout(timeout, self.on_exit)

  def run(self):
    self.prepare()


def main(argv):
  strat_config = PassUnhedgeStrategyConfig(load_clean_json(FLAGS.config_filename))
  strategy = SimpleArbStrategy(strat_config)
  signal.signal(signal.SIGTERM, strategy.signal_handler)
  signal.signal(signal.SIGINT, strategy.signal_handler)

  return strategy.run()


if __name__ == '__main__':
  flags.DEFINE_string('og_config_name', None, 'og_config_name')
  flags.DEFINE_string('config_filename', '', 'config_filename')
  flags.DEFINE_integer('exit_after_min', 1440, 'exit_after_min')
  flags.DEFINE_boolean('dry_run', False, 'dry_run')
  flags.DEFINE_integer('bias_record_cnt', 300, 'bias_record_cnt')
  flags.DEFINE_integer('bias_validate_ts_diff', 3600 * 10 ** 9, 'bias_validate_ts_diff')
  flags.DEFINE_float('bias_bar', 4.0, 'bias_bar')
  flags.DEFINE_float('leave_bias_bar', 1.0, 'leave_bias_bar')
  flags.DEFINE_float('price_diff_bar', 5.0, 'price_diff_bar')

  logging.basicConfig(level='INFO', format='%(levelname)s %(asctime)s %(name)s] %(message)s')
  app.run(main)
